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

Dev x unique #444

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
19 changes: 15 additions & 4 deletions openapiart/bundler.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ def bundle(self):
self._resolve_x_constraint()
self._resolve_x_status()
self._remove_x_include()
# TODO: restore behavior
# self._resolve_x_unique()
self._resolve_x_unique()
self._resolve_license()
self._resolve_x_enum(self._content)
self._generate_version_api_spec(self._content)
Expand Down Expand Up @@ -1154,9 +1153,21 @@ def _resolve_x_status(self):
def _resolve_x_unique(self):
"""validate the x-unique field and make sure it is [global]"""
for xunique in self._get_parser("$..x-unique").find(self._content):
if xunique.value in ["global"]:
parent_schema_object = jsonpath_ng.Parent().find(xunique)[0].value
if (
"type" in parent_schema_object
and parent_schema_object["type"] != "string"
):
raise Exception(
"invalid x-unique type %s in %s, x-unique is only allowed on string values"
% (parent_schema_object["type"], str(xunique.full_path))
)
if xunique.value in ["global", "local"]:
continue
raise Exception("x-unique can have only 'global'")
raise Exception(
"invalid value %s for x-unique in %s, x-unique can only have one of the following ['global', 'local']"
% (xunique.value, str(xunique.full_path))
)

def _resolve_x_constraint(self):
"""Find all instances of x-constraint in the openapi content
Expand Down
59 changes: 31 additions & 28 deletions openapiart/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ type httpClient struct {
// All methods that perform validation will add errors here
// All api rpcs MUST call Validate
type Constraints interface {
ValueOf(name string) interface{}
Warnings() []string
}

type validation struct {
Expand Down Expand Up @@ -427,34 +427,37 @@ func (obj *validation) validateHexSlice(hex []string) error {
return obj.validateSlice(hex, "hex")
}

// TODO: restore behavior
// func (obj *validation) createMap(objName string) {
// if obj.constraints == nil {
// obj.constraints = make(map[string]map[string]Constraints)
// }
// _, ok := obj.constraints[objName]
// if !ok {
// obj.constraints[objName] = make(map[string]Constraints)
// }
// }

// TODO: restore behavior
// func (obj *validation) isUnique(objectName, value string, object Constraints) bool {
// if value == "" {
// return true
// }
func (obj *validation) createMap(objName string) {
if obj.constraints == nil {
obj.constraints = make(map[string]map[string]Constraints)
}
_, ok := obj.constraints[objName]
if !ok {
obj.constraints[objName] = make(map[string]Constraints)
}
}

// obj.createMap("globals")
// _, ok := obj.constraints["globals"][value]
// unique := false
// if !ok {
// obj.constraints["globals"][value] = object
// obj.createMap(objectName)
// obj.constraints[objectName][value] = object
// unique = true
// }
// return unique
// }
func (obj *validation) isUnique(objectName, value string, scope string, object Constraints) bool {
if value == "" {
return true
}
key := ""
if scope == "global" {
key = scope
} else {
key = objectName
}
obj.createMap(key)
_, ok := obj.constraints[key][value]
unique := false
if !ok {
obj.constraints[key][value] = object
// obj.createMap(objectName)
// obj.constraints[objectName][value] = object
unique = true
}
return unique
}

// TODO: restore behavior
// func (obj *validation) validateConstraint(objectName []string, value string) bool {
Expand Down
110 changes: 78 additions & 32 deletions openapiart/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import types
import platform
from google.protobuf import json_format
from inspect import stack
import sanity_pb2_grpc as pb2_grpc
import sanity_pb2 as pb2

Expand Down Expand Up @@ -276,8 +277,7 @@ def serialize(self, encoding=JSON):
encoding. The json and yaml encodings will return a str object and
the dict encoding will return a python dict object.
"""
# TODO: restore behavior
# self._clear_globals()
self._clear_globals()
if encoding == OpenApiBase.JSON:
data = json.dumps(self._encode(), indent=2, sort_keys=True)
elif encoding == OpenApiBase.YAML:
Expand All @@ -286,8 +286,7 @@ def serialize(self, encoding=JSON):
data = self._encode()
else:
raise NotImplementedError("Encoding %s not supported" % encoding)
# TODO: restore behavior
# self._validate_coded()
self._validate_coded()
return data

def _encode(self):
Expand All @@ -310,13 +309,11 @@ def deserialize(self, serialized_object):
- obj(OpenApiObject): This object with all the
serialized_object deserialized within.
"""
# TODO: restore behavior
# self._clear_globals()
self._clear_globals()
if isinstance(serialized_object, (str, unicode)):
serialized_object = yaml.safe_load(serialized_object)
self._decode(serialized_object)
# TODO: restore behavior
# self._validate_coded()
self._validate_coded()
return self

def _decode(self, dict_object):
Expand Down Expand Up @@ -562,29 +559,46 @@ def types_validation(
raise TypeError(err_msg)

def _validate_unique_and_name(self, name, value, latter=False):

if self._TYPES[name].get("unique") is None or value is None:
return

unique_type = self._TYPES[name]["unique"]
class_name = self.__class__.__name__

if latter is True:
self.__validate_latter__["unique"].append(
if (
unique_type == "local"
and class_name in self.__validate_latter__
):
key = class_name
else:
key = "unique"
self.__validate_latter__[key].append(
(self._validate_unique_and_name, name, value)
)
return
class_name = type(self).__name__
unique_type = self._TYPES[name]["unique"]
if class_name not in self.__constraints__:
self.__constraints__[class_name] = dict()

# class_name = type(self).__name__

# if class_name not in self.__constraints__:
# self.__constraints__[class_name] = dict()

values = None
if unique_type == "global":
values = self.__constraints__["global"]
else:
elif class_name in self.__constraints__:
values = self.__constraints__[class_name]
# else:
# values = self.__constraints__["class_name"]
if value in values:
self._validation_errors.append(
"{} with {} already exists".format(name, value)
)
return
if isinstance(values, list):
values.append(value)
self.__constraints__[class_name].update({value: self})
# self.__constraints__[class_name].update({value: self})

def _validate_constraint(self, name, value, latter=False):
cons = self._TYPES[name].get("constraint")
Expand All @@ -609,12 +623,21 @@ def _validate_constraint(self, name, value, latter=False):
)
return

def _validate_coded(self):
for item in self.__validate_latter__["unique"]:
item[0](item[1], item[2])
for item in self.__validate_latter__["constraint"]:
item[0](item[1], item[2])
self._clear_vars()
def _validate_coded(self, check_local_unique=False, class_name=None):
if check_local_unique:
for item in self.__validate_latter__[class_name]:
item[0](item[1], item[2])
if class_name in self.__constraints__:
del self.__constraints__[class_name]
elif class_name in self.__validate_latter__:
del self.__validate_latter__[class_name]
return
else:
for item in self.__validate_latter__["unique"]:
item[0](item[1], item[2])
for item in self.__validate_latter__["constraint"]:
item[0](item[1], item[2])
self._clear_vars()
if len(self._validation_errors) > 0:
errors = "\n".join(self._validation_errors)
self._clear_errors()
Expand All @@ -636,6 +659,11 @@ def _clear_globals(self):
continue
del self.__constraints__[k]

keys = list(self.__validate_latter__.keys())
for k in keys:
if k not in ["unique", "constraint"]:
del self.__validate_latter__[k]


class OpenApiObject(OpenApiBase, OpenApiValidator):
"""Base class for any /components/schemas object
Expand Down Expand Up @@ -749,8 +777,8 @@ def _encode(self):
self._validate_required()
for key, value in self._properties.items():
self._validate_types(key, value)
self._validate_unique_and_name(key, value, True)
# TODO: restore behavior
# self._validate_unique_and_name(key, value, True)
# self._validate_constraint(key, value, True)
if isinstance(value, (OpenApiObject, OpenApiIter)):
output[key] = value._encode()
Expand Down Expand Up @@ -797,10 +825,21 @@ def _decode(self, obj):
and self._TYPES[property_name]["type"] not in dtypes
):
child = self._get_child_class(property_name, True)

# placeholders added for checking local unique
class_name = child[1].__name__
self.__constraints__[class_name] = []
self.__validate_latter__[class_name] = []
openapi_list = child[0]()
for item in property_value:
item = child[1]()._decode(item)
openapi_list._items.append(item)

# checking local unique
self._validate_coded(
check_local_unique=True, class_name=class_name
)

property_value = openapi_list
ignore_warnings = True
elif (
Expand All @@ -824,15 +863,11 @@ def _decode(self, obj):
):
property_value = [int(v) for v in property_value]
self._properties[property_name] = property_value
# TODO: restore behavior
# OpenApiStatus.warn(
# "{}.{}".format(type(self).__name__, property_name), self
# )
if not ignore_warnings:
self._raise_status_warnings(property_name, property_value)
self._validate_types(property_name, property_value)
self._validate_unique_and_name(property_name, property_value, True)
# TODO: restore behavior
# self._validate_unique_and_name(property_name, property_value, True)
# self._validate_constraint(property_name, property_value, True)
self._validate_required()
return self
Expand Down Expand Up @@ -980,8 +1015,7 @@ def validate(self):
self._validate_required()
for key, value in self._properties.items():
self._validate_types(key, value)
# TODO: restore behavior
# self._validate_coded()
self._validate_coded()

def get(self, name, with_default=False):
"""
Expand Down Expand Up @@ -1014,14 +1048,16 @@ def _raise_status_warnings(self, property_name, property_value):

return

enum_key = "%s.%s" % (property_name, property_value)
enum_key = ""
if not isinstance(property_value, OpenApiObject):
enum_key = "%s.%s" % (property_name, property_value)
if property_name in self._STATUS:
print("[WARNING]: %s" % self._STATUS[property_name])
elif enum_key in self._STATUS:
print("[WARNING]: %s" % self._STATUS[enum_key])


class OpenApiIter(OpenApiBase):
class OpenApiIter(OpenApiBase, OpenApiValidator):
"""Container class for OpenApiObject

Inheriting classes contain 0..n instances of an OpenAPI components/schemas
Expand Down Expand Up @@ -1113,7 +1149,17 @@ def set(self, index, item):
return self

def _encode(self):
return [item._encode() for item in self._items]
if len(self._items) > 0:
class_name = self._items[0].__class__.__name__
self.__constraints__[class_name] = []
self.__validate_latter__[class_name] = []
items = [item._encode() for item in self._items]
self._validate_coded(
check_local_unique=True, class_name=class_name
)
if stack()[1][3] == "__str__":
self._clear_vars()
return items

def _decode(self, encoded_list):
item_class_name = self.__class__.__name__.replace("Iter", "")
Expand Down
5 changes: 2 additions & 3 deletions openapiart/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ def _get_methods_and_factories(self):
rpc.good_response_type = response_type
rpc.proto_field_name = proto_name
rpc.http_method = path["method"]
# TODO: restore behavior
if "x-status" in path["operation"] and path["operation"][
"x-status"
].get("status") in ["deprecated", "under_review"]:
Expand Down Expand Up @@ -1795,8 +1794,8 @@ def _get_openapi_types(self, yobject):
# cons_lst.append("%s.%s" % (klass, prop.strip("/")))
# if cons_lst != []:
# pt.update({"constraint": cons_lst})
# if "x-unique" in yproperty:
# pt.update({"unique": '"%s"' % yproperty["x-unique"]})
if "x-unique" in yproperty:
pt.update({"unique": '"%s"' % yproperty["x-unique"]})
return types

def _get_required_and_defaults(self, yobject):
Expand Down
Loading