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

Protobuf parser library and first part of working tests. #7

Merged
merged 16 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions karapace/protobuf/enum_constant_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from karapace.protobuf.location import Location
from karapace.protobuf.utils import append_documentation, append_options


class EnumConstantElement:
location: Location
name: str
tag: int
documentation: str
options: list = []

def __init__(
self,
location: Location,
name: str,
tag: int,
documentation: str,
options: list,
):
self.location = location
self.name = name

self.tag = tag
self.options = options
if not documentation:
self.documentation = ""
else:
self.documentation = documentation

def to_schema(self) -> str:
result: list = list()
append_documentation(result, self.documentation)
result.append(f"{self.name} = {self.tag}")
if self.options:
result.append(" ")
append_options(result, self.options)
result.append(";\n")
return "".join(result)
35 changes: 35 additions & 0 deletions karapace/protobuf/enum_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from karapace.protobuf.location import Location
from karapace.protobuf.type_element import TypeElement
from karapace.protobuf.utils import append_documentation, append_indented


class EnumElement(TypeElement):
constants: list = []

def __init__(self, location: Location, name: str, documentation: str, options: list, constants: list):
self.location = location
self.name = name
self.documentation = documentation
self.options = options
self.constants = constants
# Enums do not allow nested type declarations.
self.nested_types = []

def to_schema(self) -> str:
result: list = list()
append_documentation(result, self.documentation)
result.append(f"enum {self.name} {{")

if self.options or self.constants:
result.append("\n")

if self.options:
for option in self.options:
append_indented(result, option.to_schema_declaration())

if self.constants:
for constant in self.constants:
append_indented(result, constant.to_schema())

result.append("}\n")
return "".join(result)
30 changes: 30 additions & 0 deletions karapace/protobuf/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
def error(message: str):
raise Exception(message)


class ProtobufParserRuntimeException(Exception):
pass


class IllegalStateException(Exception):
def __init__(self, message="IllegalStateException"):
self.message = message
super().__init__(self.message)


class IllegalArgumentException(Exception):
def __init__(self, message="IllegalArgumentException"):
self.message = message
super().__init__(self.message)


class Error(Exception):
"""Base class for errors in this module."""


class ProtobufException(Error):
"""Generic Protobuf schema error."""


class SchemaParseException(ProtobufException):
"""Error while parsing a Protobuf schema descriptor."""
27 changes: 27 additions & 0 deletions karapace/protobuf/extend_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from karapace.protobuf.location import Location
from karapace.protobuf.utils import append_documentation, append_indented


class ExtendElement:
location: Location
name: str
documentation: str
fields: list

def __init__(self, location: Location, name: str, documentation: str, fields: list):
self.location = location
self.name = name
self.documentation = documentation
self.fields = fields

def to_schema(self):
result: list = list()
append_documentation(result, self.documentation)
result.append(f"extend {self.name} {{")
if self.fields:
result.append("\n")
for field in self.fields:
append_indented(result, field.to_schema())

result.append("}\n")
return result
40 changes: 40 additions & 0 deletions karapace/protobuf/extensions_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from karapace.protobuf.kotlin_wrapper import KotlinRange
from karapace.protobuf.location import Location
from karapace.protobuf.utils import append_documentation, MAX_TAG_VALUE


class ExtensionsElement:
location: Location
documentation: str = ""
""" An [Int] or [IntRange] tag. """
values: list

def __init__(self, location: Location, documentation: str, values: list):
self.location = location
self.documentation = documentation
self.values = values

def to_schema(self) -> str:
result: list = []
append_documentation(result, self.documentation)
result.append("extensions ")

for index in range(0, len(self.values)):
value = self.values[index]
if index > 0:
result.append(", ")
if isinstance(value, int):
result.append(str(value))
# TODO: maybe replace Kotlin IntRange by list?
elif isinstance(value, KotlinRange):
result.append(f"{value.minimum} to ")
last_value = value.maximum
if last_value < MAX_TAG_VALUE:
result.append(str(last_value))
else:
result.append("max")
else:
raise AssertionError()

result.append(";\n")
return "".join(result)
10 changes: 10 additions & 0 deletions karapace/protobuf/field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# TODO: ...
from enum import Enum


class Field:
class Label(Enum):
OPTIONAL = 1
REQUIRED = 2
REPEATED = 3
ONE_OF = 4
77 changes: 77 additions & 0 deletions karapace/protobuf/field_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from karapace.protobuf.field import Field
from karapace.protobuf.location import Location
from karapace.protobuf.option_element import OptionElement
from karapace.protobuf.proto_type import ProtoType
from karapace.protobuf.utils import append_documentation, append_options


class FieldElement:
location: Location
label: Field.Label
element_type: str
name: str
default_value: str = None
json_name: str = None
tag: int = 0
documentation: str = ""
options: list = []

def __init__(
self,
location: Location,
label: Field.Label = None,
element_type: str = None,
name: str = None,
default_value: str = None,
json_name: str = None,
tag: int = None,
documentation: str = None,
options: list = None
):
self.location = location
self.label = label
self.element_type = element_type
self.name = name
self.default_value = default_value
self.json_name = json_name
self.tag = tag
self.documentation = documentation
if not options:
self.options = []
else:
self.options = options

def to_schema(self):
result: list = list()
append_documentation(result, self.documentation)

if self.label:
result.append(f"{self.label.name.lower()} ")

result.append(f"{self.element_type} {self.name} = {self.tag}")

options_with_default = self.options_with_special_values()
if options_with_default:
result.append(' ')
append_options(result, options_with_default)
result.append(";\n")

return "".join(result)

def options_with_special_values(self) -> list:
""" Both `default` and `json_name` are defined in the schema like options but they are actually
not options themselves as they're missing from `google.protobuf.FieldOptions`.
"""

options = self.options.copy()

if self.default_value:
proto_type: ProtoType = ProtoType.get2(self.element_type)
options.append(OptionElement("default", proto_type.to_kind(), self.default_value, False))
if self.json_name:
self.options.append(OptionElement("json_name", OptionElement.Kind.STRING, self.json_name, False))

return options


# Only non-repeated scalar types and Enums support default values.
38 changes: 38 additions & 0 deletions karapace/protobuf/group_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from karapace.protobuf.field import Field
from karapace.protobuf.location import Location
from karapace.protobuf.utils import append_documentation, append_indented
from typing import Union


class GroupElement:
label: Field.Label
location: Location
name: str
tag: int
documentation: str = ""
fields: list = list()

def __init__(
self, label: Union[None, Field.Label], location: Location, name: str, tag: int, documentation: str, fields: list
):
self.label = label
self.location = location
self.name = name
self.tag = tag
self.documentation = documentation
self.fields = fields

def to_schema(self) -> str:
result: list = []
append_documentation(result, self.documentation)

# TODO: compare lower() to lowercase() and toLowerCase(Locale.US) Kotlin
if self.label:
result.append(f"{str(self.label.name).lower()} ")
result.append(f"group {self.name} = {self.tag} {{")
if self.fields:
result.append("\n")
for field in self.fields:
append_indented(result, field.to_schema())
result.append("}\n")
return "".join(result)
72 changes: 72 additions & 0 deletions karapace/protobuf/kotlin_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from karapace.protobuf.exception import IllegalArgumentException, IllegalStateException


def check(q: bool, message: str):
if not q:
raise IllegalStateException(message)


def trim_margin(s: str) -> str:
lines = s.split("\n")
new_lines = list()

for line in lines:
idx = line.find("|")
if idx < 0:
new_lines.append(line)
else:
new_lines.append(line[idx + 1:].rstrip())

if not new_lines[0].strip():
del new_lines[0]

if not new_lines[-1].strip():
del new_lines[-1]

return "\n".join(new_lines)


def require(q: bool, message: str):
if not q:
raise IllegalArgumentException(message)


def options_to_list(a: list) -> list:
# TODO
return a


class String(str):
pass


class Any:
pass


class StringBuilder(list):
def append_indented(self: list, value: str):
lines = value.split("\n")
if len(lines) > 1 and not lines[-1]:
lines = lines.pop()

for line in lines:
self.append(" ")
self.append(line)
self.append("\n")


class OptionsList(list):
pass


class KotlinRange:
minimum: int
maximum: int

def __init__(self, minimum, maximum):
self.minimum = minimum
self.maximum = maximum

def __str__(self) -> str:
return f"{self.minimum}..{self.maximum}"
Loading