-
Notifications
You must be signed in to change notification settings - Fork 15
Implement servus::Serializable::getSchema() #65
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
Conversation
dnachbaur
commented
Oct 20, 2016
- emit JSON schema from FBS in zerobufCxx.py
- create enum class for enums instead of 'old' enums; breaks existing enum names
- fix wrong JSON value for empty arrays (was 'null', is '[]' now)
rdumusc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there unit tests for the new ZeroBuf::getSchema() functionality?
bin/zerobufCxx.py
Outdated
| "::" + impl_function + | ||
| "\n{" + | ||
| NEXTLINE + self.body) | ||
| file.write('\n{0} {1}{2}\n{{{3}{4}'.format(self.ret_val, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there not an extra '{' between \n and {3} ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because .format() replaces all {} placeholders
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhhh I finally got it after testing for myself... so the single { is ESCAPED as {{, otherwise .format() complains about a missing matching }...
bin/zerobufCxx.py
Outdated
| return 'set{0}( {1}( ::zerobuf::fromJSON< {2} >( ::zerobuf::getJSONField( json, "{3}" ))));'.\ | ||
| # convert enums to their name as a string | ||
| if self.value_type.is_enum_type: | ||
| return 'set{0}( {1}( from_string( ::zerobuf::fromJSON< std::string >( ::zerobuf::getJSONField( json, "{3}" )))));'.\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{2} not used, remove?
bin/zerobufCxx.py
Outdated
| return '::zerobuf::toJSON( {0}( get{1}( )), ::zerobuf::getJSONField( json, "{2}" ));'.\ | ||
| # convert enums back from their name | ||
| if self.value_type.is_enum_type: | ||
| return '::zerobuf::toJSON( std::string( to_string( get{1}( ))), ::zerobuf::getJSONField( json, "{2}" ));'.\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, {0} not used
bin/zerobufCxx.py
Outdated
| strs.append('case {0}::{1}: return std::string( "{1}" );'.format(self.name, enumValue)) | ||
| return Function('std::string', | ||
| 'to_string( const {0}& val )'.format(self.name), | ||
| 'switch( val )\n {{\n' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace '\n ' occurences with NEXTLINE
bin/zerobufCxx.py
Outdated
| if len(attrib) == 2 and is_zerobuf_type: | ||
| member = DynamicZeroBufMember(name, value_type, dynamic_type_index) | ||
| table = next(x for x in fbsFile.tables if x.name == cxxtype) | ||
| properties[name] = table.json_schema |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused as to what these variables represent. Is 'property' the equivalent of a c++ pointer or a reference on properties[name]? can't you do "property = table.json_schema" instead of "properties[name] = table.json_schema"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's python, so it's most probably a reference. I tried the same assignment as you described, and as it didn't work, I fell back to the code I put.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
property = table.json_schema can't work. In python all variables are pointers (and argument passing could be called pointer by value)
bin/zerobufCxx.py
Outdated
| if is_byte_type: | ||
| property['type'] = 'string' | ||
| property['media'] = {} | ||
| property['media']['binaryEncoding'] = 'base64' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these three lines are a copy-paste of the other "if is_byte_type" above, maybe can be combined somehow?
|
|
||
| if fbs in ['float', 'double']: | ||
| return 'number' | ||
| return 'integer' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funny naming... that means that for json an integer is not a number.
bin/zerobufCxx.py
Outdated
| NEXTLINE + self.body) | ||
| file.write('\n{0} {1}{2}\n{{{3}{4}'.format(self.ret_val, | ||
| (classname + '::') if classname else '', impl_function, | ||
| NEXTLINE, self.body)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would write self.body in a separate file.write call, not so much for performance, but for readability.
I would even replace NEXTLINE with a function call startNextLine(file) to reduce the number of string concatenations (In some places the look ugly).
bin/zerobufCxx.py
Outdated
| NEXTLINE + self.body + "\n") | ||
| file.write('\n{0}{1}{2}{3}\n' | ||
| .format((classname + '::') if classname else '', impl_function, | ||
| NEXTLINE, self.body)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
bin/zerobufCxx.py
Outdated
| # convert enums to their name as a string | ||
| if self.value_type.is_enum_type: | ||
| return 'set{0}( {1}( from_string( ::zerobuf::fromJSON< std::string >( ::zerobuf::getJSONField( json, "{3}" )))));'.\ | ||
| format(self.cxxName, self.value_type.type, self.value_type.get_data_type(), self.cxxname) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format string doesn't have any {2}, but you are passing four parameters, review.
bin/zerobufCxx.py
Outdated
| # convert enums back from their name | ||
| if self.value_type.is_enum_type: | ||
| return '::zerobuf::toJSON( std::string( to_string( get{1}( ))), ::zerobuf::getJSONField( json, "{2}" ));'.\ | ||
| format(self.value_type.get_data_type(), self.cxxName, self.cxxname) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Format string has no {0}
bin/zerobufCxx.py
Outdated
| def parse_members(self, fbsFile): | ||
| dynamic_type_index = 0 | ||
| self.json_schema['properties'] = {} | ||
| properties = self.json_schema['properties'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
properties = {}
self.json_schema['properties'] = properties
?
bin/zerobufCxx.py
Outdated
| value_type = ValueType(cxxtype, cxxtype_size, is_zerobuf_type, is_enum_type, is_byte_type) | ||
|
|
||
| properties[name] = {} | ||
| property = properties[name] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
bin/zerobufCxx.py
Outdated
| if self.is_dynamic(attrib, fbsFile): | ||
| if len(attrib) == 2 and is_zerobuf_type: | ||
| member = DynamicZeroBufMember(name, value_type, dynamic_type_index) | ||
| table = next(x for x in fbsFile.tables if x.name == cxxtype) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this code a bit hard to understand (not the code itself, but why you do this). Can you add some comments to explain it.
| dynamic_type_index += 1 | ||
| self.dynamic_members.append(member) | ||
| else: | ||
| if len(attrib) == 2 or len(attrib) == 3: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ifs in this function are quite cryptic, but this is the one I understand less. Can you add comments to know what are the cases that you are dealing with in each branch?
| dynamic_type_index = 0 | ||
| self.json_schema['properties'] = {} | ||
| properties = self.json_schema['properties'] | ||
| for attrib in self.attributes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to suggest a big change in this for loop, feel free to do it, but I think it will improve readability. Actually it's two different proposals, if you don't want to do it any of them at least you should add more comments because the code it's quite convoluted.
Here I understand that you are doing two independent things in the same loop and it gets big. For readability I think it's better to separate the two processings. The easiest way to do it is to have two separate loops (despite the if sentences are going to be the same), one for the original code and another for the new stuff about properties. For maintainability the loop code should be refactored, and the only way I can imagine is using something similar to a visitor factor. In python you do it without to much hassle. Create a function for doing the loop and the if parts, this function takes as an argument a list of objects with callbacks to deal with each specific case. Declare two classes, one with the callbacks for the original code and another for the properties. With proper function names you won't need to write any single comment.
bin/zerobufCxx.py
Outdated
| '\n default: throw std::runtime_error( "{1}" );\n }}' | ||
| .format(NEXTLINE.join(strs), 'Unknown value for enum {0}'.format(self.name)), split=True) | ||
| 'switch( val ){0}{{\n' | ||
| ' {1}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is still one NEXTLINE hiding here
bin/zerobufCxx.py
Outdated
| property['media'] = {} | ||
| property['media']['binaryEncoding'] = 'base64' | ||
| if is_byte_type: # byte arrays are base64 strings | ||
| property = _add_base64_string(property) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I understand correctly the assignment and function return statement are redundant, since the function already modifies the property object passed to it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function arguments are passed by copy, hence I found this 'pattern' to mimic a in-place modification :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just discovered that it is more subtle than that, you can't really reason in C++ terms of copy or reference with the python 'variables', they are more like 'labels' pointing to objects which are either mutable, or immutable. In this case 'property' is mutable (it's like a list) and it is modified by the function - the assignment and return value have no effect (it just reassigns 'property' to 'property'). Some references:
http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables
http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference/8140747#8140747
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rdumusc is right, property = _add_base64_string(property) doesn't make sense in Python. Functions arguments are not passed by copy, what is copied is the pointer to the object. That means you can't change the variable at the caller scope (but remind the variable is just a pointer), but you can change the object it points to.
|
The code is good now, but what about the unit tests? |
|
Good point. |
|
On the other hand, the code coverage has not been affected. But I guess you want me to test the generated JSON schema. |
|
Yes indeed, I meant testing the new JSON schema part. This is really the sort of code which is well suited to unit testing. |
|
...almost there, the last thing missing are tests for the new enum-to-string conversion functions: |
* emit JSON schema from FBS in zerobufCxx.py * create enum class for enums instead of 'old' enums; breaks existing enum names * fix wrong JSON value for empty arrays (was 'null', is '[]' now)
|
retest this please |