Source code for stix2generator.exceptions

import stix2generator.utils


[docs]class GenerationError(Exception): """ Base class for all object and graph generation (via the prototyping language) errors. """ pass
[docs]class RegistryNotFoundError(GenerationError): """ Represents failure to find an object generator specification registry for the given STIX version. """ def __init__(self, stix_version): super().__init__( "Object specification registry not found for STIX version " + stix_version ) self.stix_version = stix_version
[docs]class LanguageError(GenerationError): """ Base class for exceptions thrown while processing the STIX prototyping language. Instances may have line/column attributes giving the location of the error, if known/applicable. """ def __init__(self, message, parse_tree_meta=None): """ Initialize this error. If metadata is given, it will be used to decorate the message with additional line/column number information. The line and column information will also be assigned to same-named properties of the exception object. :param message: The error message. :param parse_tree_meta: Metadata from the parse tree which contains line/column number information. This must be an object with "line" and "column" attributes, or None if that information is not known or applicable. """ if parse_tree_meta: self.line = parse_tree_meta.line self.column = parse_tree_meta.column message = "{}:{}: {}".format( self.line, self.column, message ) else: self.line = self.column = None super().__init__(message)
[docs]class VariableError(LanguageError): """ Instances of this class represent errors regarding variable usage. """ def __init__(self, message, var_name, parse_tree_meta=None): """ Initialize this error. The name of the variable will be available as the var_name property of the exception object. :param message: The error message :param var_name: The name of the variable. If this is a Lark Token object, it can act as the metadata; parse_tree_meta is not necessary in that case. If parse_tree_meta is given, it takes priority. :param parse_tree_meta: Metadata from the parse tree, or None. See LanguageError for a description of this parameter. """ name_is_token = stix2generator.utils.is_token(var_name) if parse_tree_meta is None and name_is_token: parse_tree_meta = var_name super().__init__(message, parse_tree_meta) self.var_name = var_name.value if name_is_token else var_name
[docs]class UndeclaredVariableError(VariableError): """ Instances of this class represent use of an undeclared variable. """ def __init__(self, var_name, parse_tree_meta=None): """ Initialize this error. :param var_name: The name of the undeclared variable. See VariableError for more description of this parameter. :param parse_tree_meta: Metadata from the parse tree, or None. See LanguageError for a description of this parameter. """ message = "Undeclared variable: " + var_name super().__init__( message, var_name, parse_tree_meta )
[docs]class RedeclaredVariableError(VariableError): """ Instances of this class represent the declaration of a variable more than once. """ def __init__(self, var_name, parse_tree_meta=None): """ Initialize this error. :param var_name: The name of the redeclared variable. See VariableError for more description of this parameter. :param parse_tree_meta: Metadata from the parse tree, or None. See LanguageError for a description of this parameter. """ message = "Redeclared variable: " + var_name super().__init__( message, var_name, parse_tree_meta )
[docs]class CircularVariableDependenciesError(LanguageError): """ Instances of this class represent circular dependencies among variables. """ def __init__(self, path): """ Initialize this error. The path will be available as the same-named property of the exception object. :param path: A list of strings which gives the circular variable dependency path which was found. """ super().__init__( "Circular dependency detected in variable declarations: " + " > ".join(path) ) self.path = path
[docs]class ObjectGenerationError(GenerationError): """ Base class for object generation errors. Also used directly as a raised exception type, when there isn't a more specific object generation error subclass that's applicable. This is typically used for various types of problems with specifications. This class is written (and used) in a different way, to make it easier to have good error messages. I think there is a relatively common problem in exception handling, that at the point where an exception object is created, not all contextual information is known. Python's default behavior requires that a complete message be generated at the point the exception object is constructed. When you don't have all the contextual information at that point, it's just impossible to generate a good message. That makes it tempting to pass extra context junk into functions/methods which is only used to generate decent error messages. In simple cases, that may be fine, but in excess, it can clutter the API. An approach to improving the situation is to allow an exception to have some extra info added as it propagates through stack frames where that info is known. I first came across this idea in Boost years ago, and I decided to try a similar design here. My idea is that exception handlers can specifically add specification type and name info, if known, as the exception propagates. Then I override how the message is formatted to incorporate this additional information. """ def __init__(self, message, spec_type=None, spec_name_stack=None): """ Initialize the exception. :param message: The base exception message (no need to include spec type or name information in the message; you can pass those in separately and the message will be augmented to include that info in a standard way). :param spec_type: A spec type if known/applicable, or None if not. E.g. a JSON type or semantic name. :param spec_name_stack: A spec name or list of names if known/applicable, or None if not. This describes a chain of references. For convenience, a plain name will be treated as a length-1 list. """ super().__init__(message) self.__message = message # attrs for exception handlers to set self.spec_type = spec_type if isinstance(spec_name_stack, str): self.spec_name_stack = [spec_name_stack] else: self.spec_name_stack = spec_name_stack or [] def __str__(self): """ Overrides the base class's exception message generation. This uses the spec name/type attrs to improve the message, if they were set. """ msg_parts = [] if self.spec_name_stack: stack_desc = " > ".join( "'{}'".format(name) for name in self.spec_name_stack ) msg_parts.append( "In specification {}: ".format(stack_desc) ) if self.spec_type: msg_parts.append( "Error generating {}: ".format(self.spec_type) ) msg_parts.append(self.__message) return "".join(msg_parts)
[docs]class UnrecognizedJSONTypeError(ObjectGenerationError): """ Represents an unrecognized spec type, e.g. in a spec's "type" property. """ def __init__(self, spec_type, spec_name_stack=None): super().__init__( "unrecognized JSON type", spec_type, spec_name_stack )
[docs]class CyclicSpecificationReferenceError(ObjectGenerationError): """ Instances represent a reference cycle composed of specification names. """ def __init__(self, spec_name_cycle, spec_type=None, spec_name_stack=None): message = "Specification reference cycle detected: " + \ " > ".join(spec_name_cycle) super().__init__( message, spec_type, spec_name_stack ) self.spec_name_cycle = spec_name_cycle
[docs]class TypeMismatchError(ObjectGenerationError): """ Instances represent a spec type mismatch: a spec for a particular JSON type was required, but a spec for a different type was found instead. """ def __init__(self, expected_type, actual_type, spec_type=None, spec_name_stack=None): message = "Type mismatch: expected '{}' but got '{}'".format( expected_type, actual_type ) super().__init__( message, spec_type, spec_name_stack ) self.expected_type = expected_type self.actual_type = actual_type
[docs]class SemanticValueTypeMismatchError(ObjectGenerationError): """ Instances represent a mismatch between the type of value produced by a semantic implementation, and the declared type of a specification. """ def __init__(self, semantic_name, actual_type, actual_value, spec_type, spec_name_stack=None): message = "Semantic '{}' produced a value of the wrong type: " \ "expected {}, got {}: {}".format( semantic_name, spec_type, actual_type, actual_value ) super().__init__( message, spec_type, spec_name_stack ) self.semantic_name = semantic_name self.actual_type = actual_type self.actual_value = actual_value
[docs]class SpecificationNotFoundError(ObjectGenerationError): """ A specification referred to by name was not found in the registry. """ def __init__(self, spec_name, spec_type=None, spec_name_stack=None): message = "Spec not found: '{}'".format(spec_name) super().__init__( message, spec_type, spec_name_stack ) self.spec_name = spec_name
[docs]class UndefinedPropertyError(ObjectGenerationError): """ Instances represent a reference outside a co-constraint to a property which was not defined in an object specification. (Inside a co-constraint, a different co-constraint error type is used, to indicate a bad co-constraint.) """ def __init__(self, prop_names, spec_type=None, spec_name_stack=None): # Support a single string as well, converting to a list if isinstance(prop_names, str): prop_names = [prop_names] message = "Reference to undefined property(s): " + \ ", ".join(prop_names) super().__init__( message, spec_type, spec_name_stack ) self.prop_names = prop_names
[docs]class ValueCoconstraintError(ObjectGenerationError): """ Instances represent an invalid value co-constraint. """ def __init__(self, coconstraint, message, spec_type=None, spec_name_stack=None): message = "Invalid value co-constraint '{}': {}".format( coconstraint, message ) super().__init__( message, spec_type, spec_name_stack ) self.coconstraint = coconstraint
[docs]class PresenceCoconstraintError(ObjectGenerationError): """ Instances represent an invalid presence co-constraint. """ pass
[docs]class InvalidPropertyGroupError(PresenceCoconstraintError): """ Instances represent an invalid property group, within a presence co-constraint definition. """ def __init__(self, group_name, message, spec_type=None, spec_name_stack=None): message = 'Invalid property group "{}": {}'.format(group_name, message) super().__init__( message, spec_type, spec_name_stack ) self.group_name = group_name
[docs]class GeneratableSTIXTypeNotFoundError(GenerationError): """ Instances of this class represent inability to find a generator spec registered with an object generator, which satisfies particular STIX-related criteria. For example, we might require any SDO or SCO type. """ def __init__(self, constraints, stix_version): """ Initialize this exception instance. :param constraints: The constraints which failed to match any specification. Must be a list of constraint values. See stix2generator.utils.random_generatable_stix_type() or .is_stix_type() for more information. :param stix_version: The STIX version which the constraints were checked relative to """ message = "Could not find an object generator specification for a " \ "STIX {} type satisfying the constraints: {}".format( stix_version, constraints ) super().__init__(message) self.constraints = constraints self.stix_version = stix_version
[docs]class PatternGenerationError(GenerationError): """ Base class for errors related to random STIX pattern generation. """ pass
[docs]class UnhandledPropertyValueType(PatternGenerationError): """ Instances represent an unhandled property value type from a stix2 object. The property value is used to create a constant in a STIX pattern, and we need to know what comparison expression operators are legal to use with it. """ def __init__(self, value): message = "Can't create comparison expression: don't know what " \ "operators apply to type {}: {}".format( type(value).__name__, str(value) ) super().__init__(message) self.value = value
[docs]class UnsupportedObjectStructureError(PatternGenerationError): """ The AST for object paths can't represent arbitrary path structure. E.g. it can't represent a list of lists. Instances of this class represent a random path taken through a STIX object, where the path contains unsupported structure. """ def __init__(self, object_type, path_elements): message = "The path through a '{}' contains unsupported structure:" \ " {}".format( object_type, repr(path_elements) ) super().__init__(message) self.object_type = object_type self.path_elements = path_elements
[docs]class UnrecognizedSTIXTypeError(PatternGenerationError): """ Instances represent a STIX SCO type given as a comparison expression type constraint, which was unrecognized. A generator spec could not be found which generates objects of that type. """ def __init__(self, stix_type): message = "Unrecognized STIX type for comparison expression type" \ " constraint: {}".format(stix_type) super().__init__(message) self.stix_type = stix_type
[docs]class InvalidRefPropertyValueError(PatternGenerationError): """ Instances represent an invalid value for a reference property. The value must be an ID from which we can extract an object type, which is used to continue an object path through the reference. """ def __init__(self, value): message = "Invalid reference property value, must be an" \ " object ID: {}".format(value) super().__init__(message) self.value = value
[docs]class AutoRegistrationError(Exception): """ Base class for errors with automatic creation and registration of Python classes as stix2 custom objects, which are derived from object generator specifications. """ def __init__(self, message, spec_name=None): super().__init__(message) self.__message = message self.spec_name = spec_name def __str__(self): msg_parts = [] if self.spec_name: msg_parts.append( "In specification {}: ".format(self.spec_name) ) msg_parts.append(self.__message) return "".join(msg_parts)
[docs]class AutoRegistrationInferenceError(AutoRegistrationError): """ For auto-registration to work, STIX object information must be inferred from an object generator specification. Instances of this class represent situations where the spec is such that inference can't be performed (even if the spec itself is valid). """ pass
[docs]class IllegalSTIXObjectSpecType(AutoRegistrationInferenceError): """ Specifications for STIX objects must be of type "object". """ def __init__(self, spec_type, spec_name=None): message = 'STIX object specs must have type "object"; got "{}"'.format( spec_type ) super().__init__(message, spec_name) self.spec_type = spec_type
[docs]class IllegalSTIXObjectPropertyType(AutoRegistrationInferenceError): """ An object generator property spec was for a JSON type which is unsupported for use as a stix2 property type. """ def __init__(self, prop_type, spec_name=None): message = 'Don\'t know how to make a stix2 property object for ' \ 'spec type "{}"'.format(prop_type) super().__init__(message, spec_name) self.prop_type = prop_type
[docs]class EmptyListError(AutoRegistrationInferenceError): def __init__(self): message = "Can't infer a list type from an empty list. The list's " \ "element type must be inferrable, so it needs at least one " \ "value." super().__init__(message)
[docs]class HeterogenousListError(AutoRegistrationInferenceError): def __init__(self, list_): message = "Can't infer a list element type from a heterogenous " \ "list: " + str(list_) super().__init__(message) self.list_ = list_