def _generate_error_handling(self, namespace, route): out = self.emit style = route.attrs.get('style', 'rpc') with self.block('if resp.StatusCode == http.StatusConflict'): # If style was download, body was assigned to a header. # Need to re-read the response body to parse the error if style == 'download': out('defer resp.Body.Close()') with self.block('body, err = ioutil.ReadAll(resp.Body);' 'if err != nil'): out('return') err_type = fmt_var(route.name) + 'APIError' if route.version != 1: err_type = '%sV%dAPIError' % (fmt_var( route.name), route.version) out('var apiError %s' % err_type) with self.block('err = json.Unmarshal(body, &apiError);' 'if err != nil'): out('return') out('err = apiError') out('return') auth_ns = "" if namespace.name == "auth" else "auth." with self.block( 'err = %sHandleCommonAuthErrors(dbx.Config, resp, body);' 'if err != nil' % auth_ns): out('return') out('err = dropbox.HandleCommonAPIErrors(dbx.Config, resp, body)') out('return')
def _generate_route(self, namespace, route): out = self.emit fn = fmt_var(route.name) err = fmt_type(route.error_data_type, namespace) out('//%sAPIError is an error-wrapper for the %s route' % (fn, route.name)) with self.block('type {fn}APIError struct'.format(fn=fn)): out('dropbox.APIError') out('EndpointError {err} `json:"error"`'.format(err=err)) out() signature = 'func (dbx *apiImpl) ' + self._generate_route_signature( namespace, route) with self.block(signature): if route.deprecated is not None: out('log.Printf("WARNING: API `%s` is deprecated")' % fn) if route.deprecated.by is not None: out('log.Printf("Use API `%s` instead")' % fmt_var(route.deprecated.by.name)) out() out('cli := dbx.Client') out() self._generate_request(namespace, route) self._generate_post() self._generate_response(route) ok_check = 'if resp.StatusCode == http.StatusOK' if fn == "Download": ok_check += ' || resp.StatusCode == http.StatusPartialContent' with self.block(ok_check): self._generate_result(route) self._generate_error_handling(route) out()
def _generate_struct_builder(self, struct): fields = [ "%s %s" % (fmt_var(field.name), fmt_type(field.data_type, struct.namespace, use_interface=True)) for field in struct.all_required_fields ] self.emit('// New{0} returns a new {0} instance'.format(struct.name)) signature = "func New{0}({1}) *{0}".format(struct.name, ', '.join(fields)) with self.block(signature): self.emit('s := new({0})'.format(struct.name)) for field in struct.all_required_fields: field_name = fmt_var(field.name) self.emit("s.{0} = {0}".format(field_name)) for field in struct.all_optional_fields: if field.has_default: if is_primitive_type(field.data_type): default = field.default if is_boolean_type(field.data_type): default = str(default).lower() self.emit('s.{0} = {1}'.format(fmt_var(field.name), default)) elif is_union_type(field.data_type): self.emit('s.%s = &%s{Tagged:dropbox.Tagged{"%s"}}' % (fmt_var(field.name), fmt_type(field.data_type, struct.namespace).lstrip('*'), field.default.tag_name)) self.emit('return s') self.emit()
def _generate_struct_builder(self, struct): fields = ["%s %s" % (fmt_var(field.name), fmt_type(field.data_type, struct.namespace, use_interface=True)) for field in struct.all_required_fields] self.emit('// New{0} returns a new {0} instance'.format(struct.name)) signature = "func New{0}({1}) *{0}".format(struct.name, ', '.join(fields)) with self.block(signature): self.emit('s := new({0})'.format(struct.name)) for field in struct.all_required_fields: field_name = fmt_var(field.name) self.emit("s.{0} = {0}".format(field_name)) for field in struct.all_optional_fields: if field.has_default: if is_primitive_type(field.data_type): default = field.default if is_boolean_type(field.data_type): default = str(default).lower() self.emit('s.{0} = {1}'.format(fmt_var(field.name), default)) elif is_union_type(field.data_type): self.emit('s.%s = &%s{Tagged:dropbox.Tagged{"%s"}}' % (fmt_var(field.name), fmt_type(field.data_type, struct.namespace).lstrip('*'), field.default.tag_name)) self.emit('return s') self.emit()
def _generate_base_type(self, base): t = fmt_type(base).lstrip('*') self.emit( '// Is{0} is the interface type for {0} and its subtypes'.format( t)) with self.block('type Is%s interface' % t): self.emit('Is%s()' % t) self.emit() self.emit('// Is{0} implements the Is{0} interface'.format(t)) self.emit("func (u *{0}) Is{0}() {{}}".format(t)) self.emit() self._generate_union_helper(base) self.emit( "// Is{0}FromJSON converts JSON to a concrete Is{0} instance". format(t)) with self.block( "func Is{0}FromJSON(data []byte) (Is{0}, error)".format(t)): name = fmt_var(t, export=False) + 'Union' self.emit("var t {0}".format(name)) with self.block("if err := json.Unmarshal(data, &t); err != nil"): self.emit("return nil, err") with self.block("switch t.Tag"): fields = base.get_enumerated_subtypes() for field in fields: with self.block('case "%s":' % field.name, delim=(None, None)): self.emit("return t.{0}, nil".format( fmt_var(field.name))) # FIX THIS self.emit("return nil, nil")
def _generate_union_helper(self, u): name = u.name namespace = u.namespace fields = u.fields if is_struct_type(u) and u.has_enumerated_subtypes(): name = fmt_var(name, export=False) + 'Union' fields = u.get_enumerated_subtypes() with self.block('type %s struct' % name): self.emit('dropbox.Tagged') for field in fields: if is_void_type(field.data_type): continue self._generate_field(field, union_field=True, namespace=namespace) self.emit() self.emit('// Valid tag values for %s' % fmt_var(u.name)) with self.block('const', delim=('(', ')')): for field in fields: self.emit('%s%s = "%s"' % (fmt_var(u.name), fmt_var(field.name), field.name)) self.emit() num_void_fields = sum([is_void_type(f.data_type) for f in fields]) # Simple structure, no need in UnmarshalJSON if len(fields) == num_void_fields: return self.emit('// UnmarshalJSON deserializes into a %s instance' % name) with self.block('func (u *%s) UnmarshalJSON(body []byte) error' % name): with self.block('type wrap struct'): self.emit('dropbox.Tagged') for field in fields: if is_void_type(field.data_type) or \ is_primitive_type(field.data_type): continue self._generate_field(field, union_field=True, namespace=namespace, raw=True) self.emit('var w wrap') with self.block('if err := json.Unmarshal(body, &w); err != nil'): self.emit('return err') self.emit('u.Tag = w.Tag') with self.block('switch u.Tag'): for field in fields: if is_void_type(field.data_type): continue field_name = fmt_var(field.name) with self.block('case "%s":' % field.name, delim=(None, None)): if is_union_type(field.data_type): with self.block('if err := json.Unmarshal' '(w.{0}, &u.{0}); err != nil' .format(field_name)): self.emit('return err') else: with self.block('if err := json.Unmarshal' '(body, &u.{0}); err != nil' .format(field_name)): self.emit('return err') self.emit('return nil') self.emit()
def _generate_route(self, namespace, route): out = self.emit fn = fmt_var(route.name) err = fmt_type(route.error_data_type, namespace) out('//%sAPIError is an error-wrapper for the %s route' % (fn, route.name)) with self.block('type {fn}APIError struct'.format(fn=fn)): out('dropbox.APIError') out('EndpointError {err} `json:"error"`'.format(err=err)) out() signature = 'func (dbx *apiImpl) ' + self._generate_route_signature( namespace, route) with self.block(signature): out('cli := dbx.Client') out() self._generate_request(namespace, route) self._generate_post(route) self._generate_response(route) with self.block('if resp.StatusCode == http.StatusOK'): self._generate_result(route) self._generate_error_handling(route) out()
def _generate_error_handling(self, route): out = self.emit style = route.attrs.get('style', 'rpc') with self.block('if resp.StatusCode == http.StatusConflict'): # If style was download, body was assigned to a header. # Need to re-read the response body to parse the error if style == 'download': out('defer resp.Body.Close()') with self.block('body, err = ioutil.ReadAll(resp.Body);' 'if err != nil'): out('return') out('var apiError %sAPIError' % fmt_var(route.name)) with self.block('err = json.Unmarshal(body, &apiError);' 'if err != nil'): out('return') out('err = apiError') out('return') out('var apiError dropbox.APIError') with self.block("if resp.StatusCode == http.StatusBadRequest || " "resp.StatusCode == http.StatusInternalServerError"): out('apiError.ErrorSummary = string(body)') out('err = apiError') out('return') with self.block('err = json.Unmarshal(body, &apiError);' 'if err != nil'): out('return') out('err = apiError') out('return')
def _generate_route(self, namespace, route): out = self.emit fn = fmt_var(route.name) err = fmt_type(route.error_data_type, namespace) out('//%sAPIError is an error-wrapper for the %s route' % (fn, route.name)) with self.block('type {fn}APIError struct'.format(fn=fn)): out('dropbox.APIError') out('EndpointError {err} `json:"error"`'.format(err=err)) out() signature = 'func (dbx *apiImpl) ' + self._generate_route_signature( namespace, route) with self.block(signature): out('cli := dbx.Client') out() self._generate_request(namespace, route) self._generate_post() self._generate_response(route) with self.block('if resp.StatusCode == http.StatusOK'): self._generate_result(route) self._generate_error_handling(route) out()
def _generate_field(self, field, union_field=False, namespace=None, raw=False): generate_doc(self, field) field_name = fmt_var(field.name) type_name = fmt_type(field.data_type, namespace, use_interface=True, raw=raw) json_tag = '`json:"%s"`' % field.name if is_nullable_type(field.data_type) or union_field: json_tag = '`json:"%s,omitempty"`' % field.name self.emit('%s %s %s' % (field_name, type_name, json_tag))
def _generate_struct(self, struct): with self.block('type %s struct' % struct.name): if struct.parent_type: self.emit( fmt_type(struct.parent_type, struct.namespace).lstrip('*')) for field in struct.fields: self._generate_field(field, namespace=struct.namespace) if struct.name in ('DownloadArg', ): self.emit( '// ExtraHeaders can be used to pass Range, If-None-Match headers' ) self.emit('ExtraHeaders map[string]string `json:"-"`') self._generate_struct_builder(struct) self.emit() if needs_base_type(struct): self.emit('// UnmarshalJSON deserializes into a %s instance' % struct.name) with self.block('func (u *%s) UnmarshalJSON(b []byte) error' % struct.name): with self.block('type wrap struct'): for field in struct.all_fields: self._generate_field(field, namespace=struct.namespace, raw=_needs_base_type( field.data_type)) self.emit('var w wrap') with self.block('if err := json.Unmarshal(b, &w); err != nil'): self.emit('return err') for field in struct.all_fields: dt = field.data_type fn = fmt_var(field.name) tn = fmt_type(dt, namespace=struct.namespace, use_interface=True) if _needs_base_type(dt): if is_list_type(dt): self.emit("u.{0} = make({1}, len(w.{0}))".format( fn, tn)) # Grab the underlying type to get the correct Is...FromJSON method tn = fmt_type(dt.data_type, namespace=struct.namespace, use_interface=True) with self.block( "for i, e := range w.{0}".format(fn)): self.emit("v, err := {1}FromJSON(e)".format( fn, tn)) with self.block('if err != nil'): self.emit('return err') self.emit("u.{0}[i] = v".format(fn)) else: self.emit("{0}, err := {1}FromJSON(w.{0})".format( fn, tn)) with self.block('if err != nil'): self.emit('return err') self.emit("u.{0} = {0}".format(fn)) else: self.emit("u.{0} = w.{0}".format(fn)) self.emit('return nil')
def _generate_result(self, route): out = self.emit if is_struct_type(route.result_data_type) and \ route.result_data_type.has_enumerated_subtypes(): out('var tmp %sUnion' % fmt_var(route.result_data_type.name, export=False)) with self.block('err = json.Unmarshal(body, &tmp);' 'if err != nil'): out('return') with self.block('switch tmp.Tag'): for t in route.result_data_type.get_enumerated_subtypes(): with self.block('case "%s":' % t.name, delim=(None, None)): self.emit('res = tmp.%s' % fmt_var(t.name)) elif not is_void_type(route.result_data_type): with self.block('err = json.Unmarshal(body, &res);' 'if err != nil'): out('return') out() out('return')
def _generate_field(self, field, union_field=False, namespace=None, raw=False): generate_doc(self, field) field_name = fmt_var(field.name) type_name = fmt_type(field.data_type, namespace, use_interface=True) json_tag = '`json:"%s"`' % field.name if is_nullable_type(field.data_type) or union_field: json_tag = '`json:"%s,omitempty"`' % field.name if raw: self.emit('%s json.RawMessage %s' % (field_name, json_tag)) else: self.emit('%s %s %s' % (field_name, type_name, json_tag))
def _generate_request(self, namespace, route): out = self.emit auth = route.attrs.get('auth', '') host = route.attrs.get('host', 'api') style = route.attrs.get('style', 'rpc') body = 'nil' if not is_void_type(route.arg_data_type): out('dbx.Config.LogDebug("arg: %v", arg)') out('b, err := json.Marshal(arg)') with self.block('if err != nil'): out('return') out() if host != 'content': body = 'bytes.NewReader(b)' if style == 'upload': body = 'content' headers = {} if not is_void_type(route.arg_data_type): if host == 'content' or style in ['upload', 'download']: headers["Dropbox-API-Arg"] = "dropbox.HTTPHeaderSafeJSON(b)" else: headers["Content-Type"] = '"application/json"' if style == 'upload': headers["Content-Type"] = '"application/octet-stream"' out('headers := map[string]string{') for k, v in sorted(headers.items()): out('\t"{}": {},'.format(k, v)) out('}') if fmt_var(route.name) == "Download": out('for k, v := range arg.ExtraHeaders { headers[k] = v }') if auth != 'noauth' and auth != 'team': with self.block('if dbx.Config.AsMemberID != ""'): out('headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID' ) out() fn = route.name if route.version != 1: fn += '_v%d' % route.version authed = 'false' if auth == 'noauth' else 'true' out('req, err := (*dropbox.Context)(dbx).NewRequest("{}", "{}", {}, "{}", "{}", headers, {})' .format(host, style, authed, namespace.name, fn, body)) with self.block('if err != nil'): out('return') out('dbx.Config.LogInfo("req: %v", req)') out()
def _generate_route_signature(self, namespace, route): req = fmt_type(route.arg_data_type, namespace) res = fmt_type(route.result_data_type, namespace, use_interface=True) fn = fmt_var(route.name) style = route.attrs.get('style', 'rpc') arg = '' if is_void_type(route.arg_data_type) else 'arg {req}' ret = '(err error)' if is_void_type(route.result_data_type) else \ '(res {res}, err error)' signature = '{fn}(' + arg + ') ' + ret if style == 'download': signature = '{fn}(' + arg + \ ') (res {res}, content io.ReadCloser, err error)' elif style == 'upload': signature = '{fn}(' + arg + ', content io.Reader) ' + ret if is_void_type(route.arg_data_type): signature = '{fn}(content io.Reader) ' + ret return signature.format(fn=fn, req=req, res=res)
def _generate_error_handling(self, route): out = self.emit with self.block('if resp.StatusCode == http.StatusConflict'): out('var apiError %sAPIError' % fmt_var(route.name)) with self.block('err = json.Unmarshal(body, &apiError);' 'if err != nil'): out('return') out('err = apiError') out('return') out('var apiError dropbox.APIError') with self.block('if resp.StatusCode == http.StatusBadRequest'): out('apiError.ErrorSummary = string(body)') out('err = apiError') out('return') with self.block('err = json.Unmarshal(body, &apiError);' 'if err != nil'): out('return') out('err = apiError') out('return')
def _generate_union_helper(self, u): name = u.name namespace = u.namespace fields = u.fields if is_struct_type(u) and u.has_enumerated_subtypes(): name = fmt_var(name, export=False) + 'Union' fields = u.get_enumerated_subtypes() with self.block('type %s struct' % name): self.emit('dropbox.Tagged') for field in fields: if is_void_type(field.data_type): continue self._generate_field(field, union_field=True, namespace=namespace) self.emit() self.emit('// Valid tag values for %s' % fmt_var(u.name)) with self.block('const', delim=('(', ')')): for field in fields: self.emit('%s%s = "%s"' % (fmt_var(u.name), fmt_var(field.name), field.name)) self.emit() num_void_fields = sum([is_void_type(f.data_type) for f in fields]) # Simple structure, no need in UnmarshalJSON if len(fields) == num_void_fields: return self.emit('// UnmarshalJSON deserializes into a %s instance' % name) with self.block('func (u *%s) UnmarshalJSON(body []byte) error' % name): with self.block('type wrap struct'): self.emit('dropbox.Tagged') for field in fields: if is_void_type(field.data_type) or \ is_primitive_type(field.data_type): continue self._generate_field(field, union_field=True, namespace=namespace, raw=True) self.emit('var w wrap') self.emit('var err error') with self.block('if err = json.Unmarshal(body, &w); err != nil'): self.emit('return err') self.emit('u.Tag = w.Tag') with self.block('switch u.Tag'): for field in fields: if is_void_type(field.data_type): continue field_name = fmt_var(field.name) with self.block('case "%s":' % field.name, delim=(None, None)): if is_union_type(field.data_type): self.emit( 'err = json.Unmarshal(w.{0}, &u.{0})'.format( field_name)) elif is_struct_type(field.data_type) and \ field.data_type.has_enumerated_subtypes(): self.emit( "u.{0}, err = Is{1}FromJSON(body)".format( field_name, field.data_type.name)) else: self.emit( 'err = json.Unmarshal(body, &u.{0})'.format( field_name)) with self.block("if err != nil"): self.emit("return err") self.emit('return nil') self.emit()
def _generate_route(self, namespace, route): out = self.emit route_name = route.name if route.version != 1: route_name += '_v%d' % route.version fn = fmt_var(route.name) if route.version != 1: fn += 'V%d' % route.version err = fmt_type(route.error_data_type, namespace) out('//%sAPIError is an error-wrapper for the %s route' % (fn, route_name)) with self.block('type {fn}APIError struct'.format(fn=fn)): out('dropbox.APIError') out('EndpointError {err} `json:"error"`'.format(err=err)) out() signature = 'func (dbx *apiImpl) ' + self._generate_route_signature( namespace, route) with self.block(signature): if route.deprecated is not None: out('log.Printf("WARNING: API `%s` is deprecated")' % fn) if route.deprecated.by is not None: replacement_fn = fmt_var(route.deprecated.by.name) if route.deprecated.by.version != 1: replacement_fn += "V%d" % route.deprecated.by.version out('log.Printf("Use API `%s` instead")' % replacement_fn) out() args = { "Host": route.attrs.get('host', 'api'), "Namespace": namespace.name, "Route": route_name, "Auth": route.attrs.get('auth', ''), "Style": route.attrs.get('style', 'rpc'), } with self.block('req := dropbox.Request'): for k, v in args.items(): out(k + ':"' + v + '",') out("Arg: {arg},".format( arg="arg" if not is_void_type(route.arg_data_type) else "nil")) out("ExtraHeaders: {headers},".format( headers="arg.ExtraHeaders" if fmt_var(route.name) == "Download" else "nil")) out() out("var resp []byte") out("var respBody io.ReadCloser") out("resp, respBody, err = (*dropbox.Context)(dbx).Execute(req, {body})" .format(body="content" if route.attrs.get('style', '') == 'upload' else "nil")) with self.block("if err != nil"): out("var appErr {fn}APIError".format(fn=fn)) out("err = {auth}ParseError(err, &appErr)".format( auth="auth." if namespace.name != "auth" else "")) with self.block("if err == &appErr"): out("err = appErr") out("return") out() if is_struct_type( route.result_data_type ) and route.result_data_type.has_enumerated_subtypes(): out('var tmp %sUnion' % fmt_var(route.result_data_type.name, export=False)) with self.block('err = json.Unmarshal(resp, &tmp);' 'if err != nil'): out('return') with self.block('switch tmp.Tag'): for t in route.result_data_type.get_enumerated_subtypes(): with self.block('case "%s":' % t.name, delim=(None, None)): self.emit('res = tmp.%s' % fmt_var(t.name)) elif not is_void_type(route.result_data_type): with self.block('err = json.Unmarshal(resp, &res);' 'if err != nil'): out('return') out() else: out("_ = resp") if route.attrs.get('style', 'rpc') == "download": out("content = respBody") else: out("_ = respBody") out('return') out()
def _generate_union_helper(self, u): name = u.name namespace = u.namespace # Unions can be inherited, but don't need to be polymorphic. # So let's flatten out all the inherited fields. fields = u.all_fields if is_struct_type(u) and u.has_enumerated_subtypes(): name = fmt_var(name, export=False) + 'Union' fields = u.get_enumerated_subtypes() with self.block('type %s struct' % name): self.emit('dropbox.Tagged') for field in fields: if is_void_type(field.data_type): continue self._generate_field(field, union_field=True, namespace=namespace) self.emit() self.emit('// Valid tag values for %s' % fmt_var(u.name)) with self.block('const', delim=('(', ')')): for field in fields: self.emit('%s%s = "%s"' % (fmt_var(u.name), fmt_var(field.name), field.name)) self.emit() num_void_fields = sum([is_void_type(f.data_type) for f in fields]) # Simple structure, no need in UnmarshalJSON if len(fields) == num_void_fields: return self.emit('// UnmarshalJSON deserializes into a %s instance' % name) with self.block('func (u *%s) UnmarshalJSON(body []byte) error' % name): with self.block('type wrap struct'): self.emit('dropbox.Tagged') for field in fields: if is_void_type(field.data_type) or ( is_struct_type(field.data_type) and not _needs_base_type(field.data_type)): # pure structures are flattened in the containing union json blob and thus are loaded from body continue # sub-unions must be handled as RawMessage, which will be loaded into correct implementation later self._generate_field(field, union_field=True, namespace=namespace, raw=_needs_base_type(field.data_type)) self.emit('var w wrap') self.emit('var err error') with self.block('if err = json.Unmarshal(body, &w); err != nil'): self.emit('return err') self.emit('u.Tag = w.Tag') with self.block('switch u.Tag'): for field in fields: if is_void_type(field.data_type): continue field_name = fmt_var(field.name) with self.block('case "%s":' % field.name, delim=(None, None)): if _needs_base_type(field.data_type): self.emit("u.{0}, err = Is{1}FromJSON(w.{0})" .format(field_name, field.data_type.name)) elif is_struct_type(field.data_type): self.emit('err = json.Unmarshal(body, &u.{0})' .format(field_name)) else: self.emit('u.{0} = w.{0}'.format(field_name)) with self.block("if err != nil"): self.emit("return err") self.emit('return nil') self.emit()