|
| 1 | += Dropbox Go SDK Generator |
| 2 | + |
| 3 | +This repository is used to generate the (currently private) https://github.com/dropbox/dropbox-sdk-go[Dropbox Go SDK]. It does not contain any auto-generated code. |
| 4 | + |
| 5 | +== Requirements |
| 6 | + |
| 7 | + * While not a hard requirement, this repo currently assumes `python3` in the path. |
| 8 | + * Requires https://godoc.org/golang.org/x/tools/cmd/goimports[goimports] to fix up imports in the auto-generated code |
| 9 | + |
| 10 | +== Basic Setup |
| 11 | + |
| 12 | + . Clone this repo |
| 13 | + . Run `git submodule init` followed by `git submodule update` |
| 14 | + . Run `./codegen.sh` to generate code under `$GOPATH/src/github.com/dropbox/dropbox-sdk-go` |
| 15 | + |
| 16 | +== Generated Code |
| 17 | + |
| 18 | +=== Structs |
| 19 | + |
| 20 | +Babel https://github.com/braincore/babel-docs/blob/master/doc/lang_ref.rst#struct[structs] are represented as Go https://gobyexample.com/structs[structs] in a relatively straigh-forward manner. |
| 21 | + |
| 22 | +---- |
| 23 | +struct Account <1> |
| 24 | + "The amount of detail revealed about an account depends on the user |
| 25 | + being queried and the user making the query." <2> |
| 26 | +
|
| 27 | + account_id AccountId <3> |
| 28 | + "The user's unique Dropbox ID." <4> |
| 29 | + name Name <5> |
| 30 | + "Details of a user's name." |
| 31 | +---- |
| 32 | +<1> A struct is defined as a Go struct |
| 33 | +<2> The documentation shows up before the struct definition |
| 34 | +<3> Each struct member is exported and also gets assigned the correct json tag. The latter is used for serializing requests and deserializing responses. |
| 35 | +<4> Member documentation appears above the member definition |
| 36 | +<5> Non-primitive types are represented as pointers to the corresponding type |
| 37 | + |
| 38 | +[source,go] |
| 39 | +---- |
| 40 | +// The amount of detail revealed about an account depends on the user being |
| 41 | +// queried and the user making the query. <2> |
| 42 | +type Account struct { // <1> |
| 43 | + // The user's unique Dropbox ID. <4> |
| 44 | + AccountId string `json:"account_id"` // <3> |
| 45 | + // Details of a user's name. |
| 46 | + Name *Name `json:"name"` // <5> |
| 47 | +} |
| 48 | +---- |
| 49 | + |
| 50 | +=== Unions |
| 51 | + |
| 52 | +Babel https://github.com/braincore/babel-docs/blob/master/doc/lang_ref.rst#union[unions] are bit more complex as Go doesn't have native support for union types (tagged or otherwise). We declare a union as a Go struct with all the possible fields as pointer types, and then use the tag value to populate the correct field during deserialization. This necessitates the use of an intermedia wrapper struct for the deserialization to work correctly, see below for a concrete example. |
| 53 | + |
| 54 | +---- |
| 55 | +union SpaceAllocation |
| 56 | + "Space is allocated differently based on the type of account." |
| 57 | +
|
| 58 | + individual IndividualSpaceAllocation |
| 59 | + "The user's space allocation applies only to their individual account." |
| 60 | + team TeamSpaceAllocation |
| 61 | + "The user shares space with other members of their team." |
| 62 | +---- |
| 63 | + |
| 64 | +[source,go] |
| 65 | +---- |
| 66 | +// Space is allocated differently based on the type of account. |
| 67 | +type SpaceAllocation struct { // <1> |
| 68 | + Tag string `json:".tag"` // <2> |
| 69 | + // The user's space allocation applies only to their individual account. |
| 70 | + Individual *IndividualSpaceAllocation `json:"individual,omitempty"` // <3> |
| 71 | + // The user shares space with other members of their team. |
| 72 | + Team *TeamSpaceAllocation `json:"team,omitempty"` |
| 73 | +} |
| 74 | +
|
| 75 | +func (u *SpaceAllocation) UnmarshalJSON(body []byte) error { // <4> |
| 76 | + type wrap struct { // <5> |
| 77 | + Tag string `json:".tag"` |
| 78 | + // The user's space allocation applies only to their individual account. |
| 79 | + Individual json.RawMessage `json:"individual"` // <6> |
| 80 | + // The user shares space with other members of their team. |
| 81 | + Team json.RawMessage `json:"team"` |
| 82 | + } |
| 83 | + var w wrap |
| 84 | + if err := json.Unmarshal(body, &w); err != nil { // <7> |
| 85 | + return err |
| 86 | + } |
| 87 | + u.Tag = w.Tag |
| 88 | + switch w.Tag { |
| 89 | + case "individual": |
| 90 | + { |
| 91 | + if err := json.Unmarshal(body, &u.Individual); err != nil { // <8> |
| 92 | + return err |
| 93 | + } |
| 94 | + } |
| 95 | + case "team": |
| 96 | + { |
| 97 | + if err := json.Unmarshal(body, &u.Team); err != nil { |
| 98 | + return err |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + return nil |
| 103 | +} |
| 104 | +---- |
| 105 | +<1> A babel union is represented as Go struct |
| 106 | +<2> The tag value is used to determine which field is actually set |
| 107 | +<3> Possible values are represented as pointer types. Note the `omitempty` in the JSON tag; this is so values not set are automatically elided during serialization. |
| 108 | +<4> We generate a custom `UnmarshalJSON` method for union types |
| 109 | +<5> An intermedia wrapper struct is used to help with deserialization |
| 110 | +<6> Note that members of the wrapper struct are of type `RawMessage` so we can capture the body without deserializing it right away |
| 111 | +<7> When we deserialize response into the wrapper struct, it should get the tag value and the raw JSON as part of the members. |
| 112 | +<8> We then use the tag value to deserialize the `RawMessage` into the appropriate member of the union type |
| 113 | + |
| 114 | +=== Struct with Enumerated Subtypes |
| 115 | + |
| 116 | +Per the https://github.com/braincore/babel-docs/blob/master/doc/lang_ref.rst#struct-with-enumerated-subtypes[spec], structs with enumerated subtypes are a mechanism of inheritance: |
| 117 | + |
| 118 | +> If a struct enumerates its subtypes, an instance of any subtype will satisfy the type constraint. |
| 119 | + |
| 120 | +So to represent structs with enumerated subtypes in Go, we use a strategy similar to unions. The _base_ struct (the one that defines the subtypes) is represented like we represent a union above. The _subtype_ is represented as a struct that essentially duplicates all fields of the base type and includes fields specific to that subtype. Here's an example: |
| 121 | + |
| 122 | +---- |
| 123 | +struct Metadata |
| 124 | + "Metadata for a file or folder." |
| 125 | +
|
| 126 | + union |
| 127 | + file FileMetadata |
| 128 | + folder FolderMetadata |
| 129 | + deleted DeletedMetadata # Used by list_folder* and search |
| 130 | +
|
| 131 | + name String #<1> |
| 132 | + "The last component of the path (including extension). |
| 133 | + This never contains a slash." |
| 134 | +
|
| 135 | +... |
| 136 | +struct FileMetadata extends Metadata |
| 137 | + id Id? #<2> |
| 138 | + ... |
| 139 | +---- |
| 140 | +<1> Field common to all subtypes |
| 141 | +<2> Field specific to `FileMetadata` |
| 142 | + |
| 143 | + |
| 144 | +[source,go] |
| 145 | +---- |
| 146 | +// Metadata for a file or folder. |
| 147 | +type Metadata struct { // <1> |
| 148 | + Tag string `json:".tag"` |
| 149 | + File *FileMetadata `json:"file,omitempty"` |
| 150 | + Folder *FolderMetadata `json:"folder,omitempty"` |
| 151 | + Deleted *DeletedMetadata `json:"deleted,omitempty"` |
| 152 | +} |
| 153 | +... |
| 154 | +
|
| 155 | +type FileMetadata struct { |
| 156 | + // The last component of the path (including extension). This never contains a |
| 157 | + // slash. |
| 158 | + Name string `json:"name"` // <2> |
| 159 | + ... |
| 160 | + Id string `json:"id,omitempty"` // <3> |
| 161 | +} |
| 162 | +---- |
| 163 | +<1> Subtype is represented like we represent unions as described above |
| 164 | +<2> Common fields are duplicated in subtypes |
| 165 | +<3> Subtype specific fields are included as usual in structs |
| 166 | + |
| 167 | +== Known Issues |
| 168 | + |
| 169 | + * Wildcard unions not supported |
| 170 | + * Min/max constraints on strings are not enforced |
0 commit comments