diff --git a/docs/api.rst b/docs/api.rst index 52bceb90d..64df74e97 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -65,4 +65,4 @@ Schema :members: FieldStringSchemaType, FieldBooleanSchemaType, FieldIntegerSchemaType, FieldNumberSchemaType, FieldMultiValueSchemaType, - ExtraLinkSchemaType, ExtraLinkItemSchemaType + LinkSchemaType, LinkItemSchemaType diff --git a/docs/changelog.rst b/docs/changelog.rst index 724fd6234..4ad305897 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -18,6 +18,13 @@ Changelog Ensure that file paths originating from a :ref:`needs_from_toml` file are relative to that file, rather than the :file:`conf.py` file +- ✨ Add new ``needs_links`` configuration (dict-based) as replacement for ``needs_extra_links`` (list-based) + + The new :ref:`needs_links` configuration uses a dictionary mapping link name to configuration, + removing the redundant ``option`` key required in :ref:`needs_extra_links`. + Also adds support for a ``description`` field for link types. + The old ``needs_extra_links`` configuration is now deprecated but remains supported for backward compatibility. + .. _`release:6.2.0`: 6.2.0 diff --git a/docs/configuration.rst b/docs/configuration.rst index 674899073..bb1052dc6 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -339,7 +339,7 @@ For ``predicates``, the match expression is a string, using Python syntax, that - ``is_external`` (``bool``) - ``is_import`` (``bool``) - :ref:`needs_fields` -- :ref:`needs_extra_links` (``tuple[str, ...]``) +- :ref:`needs_links` (``tuple[str, ...]``) - :ref:`needs_filter_data` For example: @@ -360,58 +360,58 @@ For example: }, } -.. _`needs_extra_links`: +.. _`needs_links`: -needs_extra_links -~~~~~~~~~~~~~~~~~ +needs_links +~~~~~~~~~~~ -.. versionadded:: 0.3.11 +.. versionadded:: 7.0.0 -Allows the definition of additional link types. +Allows the definition of additional link types as a dictionary mapping link name to configuration. -Each configured link should define: +Each configured link can define: -- ``option``: The name of the option. Example "blocks". +- ``description`` (optional): A description of the link type. - ``predicates`` (optional): A list of ``(match expression, value)`` tuples. If specified, these will be evaluated in order for any need that does not explicitly set the field, with the first match setting the field value. - ``default`` (optional): A default value for the field. If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates. - ``parse_variants``: If set to ``True``, the field will support :ref:`variant options `. Default: ``False``. -- ``incoming`` (optional): Incoming text, to use for incoming links. E.g. "is blocked by". -- ``outgoing`` (optional): Outgoing text, to use for outgoing links. E.g. "blocks". +- ``incoming`` (optional): Incoming text, to use for incoming links. E.g. "is blocked by". Default: " incoming". +- ``outgoing`` (optional): Outgoing text, to use for outgoing links. E.g. "blocks". Default: "". - ``copy`` (optional): True/False. If True, the links will be copied also to the common link-list (link type ``links``). - Default: True + Default: False. - ``allow_dead_links`` (optional): True/False. If True, dead links are allowed and do not throw a warning. See :ref:`allow_dead_links` for details. Default: False. - ``style`` (optional): A plantuml style description, e.g. "#FFCC00". Used for :ref:`needflow`. See :ref:`links_style`. - ``style_part`` (optional): Same as ``style``, but get used if link is connected to a :ref:`need_part`. See :ref:`links_style`. +- ``style_start`` (optional): See :ref:`needflow_style_start`. +- ``style_end`` (optional): See :ref:`needflow_style_start`. Configuration example: .. code-block:: python - needs_extra_links = [ - { - "option": "checks", - }, - { - "option": "triggers", - "predicates": [ - ('"urgent" in tags', ["ID999", "ID998"]), - ], - "default": ["ID123"], - "incoming": "is triggered by", - "outgoing": "triggers", - "copy": False, - "allow_dead_links": True, - "style": "#00AA00", - "style_part": "#00AA00", - "style_start": "-", - "style_end": "--o", - } - ] + needs_links = { + "checks": {}, + "triggers": { + "description": "Links to needs that are triggered by this need", + "predicates": [ + ('"urgent" in tags', ["ID999", "ID998"]), + ], + "default": ["ID123"], + "incoming": "is triggered by", + "outgoing": "triggers", + "copy": False, + "allow_dead_links": True, + "style": "#00AA00", + "style_part": "#00AA00", + "style_start": "-", + "style_end": "--o", + }, + } The above example configuration allows the following usage: @@ -425,8 +425,8 @@ The above example configuration allows the following usage: :checks: EXTRA_REQ_001, DEAD_LINK_NOT_ALLOWED :triggers: DEAD_LINK -Link types with option-name **links** and **parent_needs** are added by default. -You are free to overwrite the default config by defining your own type with option name **links** or **parent_needs**. +Link types with name **links** and **parent_needs** are added by default. +You are free to overwrite the default config by defining your own type with name **links** or **parent_needs**. This type will be used as default configuration for all links. Default values @@ -447,26 +447,25 @@ For ``predicates``, the match expression is a string, using Python syntax, that - ``is_external`` (``bool``) - ``is_import`` (``bool``) - :ref:`needs_fields` -- :ref:`needs_extra_links` (``tuple[str, ...]``) +- :ref:`needs_links` (``tuple[str, ...]``) - :ref:`needs_filter_data` For example: .. code-block:: python - needs_extra_links = [ - { - "option": "custom_link", - "predicates": [ - # if status is "done", set to ["ID123"] - ("status == 'done'", ["ID123"]), - # else if status is "ongoing", set to a dynamic value - ("status == 'ongoing'", '[[copy("yyy")]]'), - ], - # the base default is a dynamic value - "default": '[[copy("xxx")]]', - }, - ] + needs_links = { + "custom_link": { + "predicates": [ + # if status is "done", set to ["ID123"] + ("status == 'done'", ["ID123"]), + # else if status is "ongoing", set to a dynamic value + ("status == 'ongoing'", '[[copy("yyy")]]'), + ], + # the base default is a dynamic value + "default": '[[copy("xxx")]]', + }, + } .. _`allow_dead_links`: @@ -547,18 +546,17 @@ Use ``style_start`` and ``style_end`` like this: .. code-block:: python - needs_extra_links = [ - { - "option": "tests", - "incoming": "is tested by", - "outgoing": "tests", - "copy": False, - "style_start": "<-", - "style_end": "down-->", - "style": "#00AA00", - "style_part": "dotted,#00AA00", - } - ] + needs_links = { + "tests": { + "incoming": "is tested by", + "outgoing": "tests", + "copy": False, + "style_start": "<-", + "style_end": "down-->", + "style": "#00AA00", + "style_part": "dotted,#00AA00", + }, + } .. note:: @@ -845,9 +843,9 @@ If you do not set ``needs_report_template``, the default template used is: {% endif %} {# Output for needs_types #} - {# Output for needs_extra_links #} + {# Output for needs_links #} {% if links|length != 0 %} - .. dropdown:: Need Extra Links + .. dropdown:: Need Links :class: needs_report_table .. list-table:: @@ -867,11 +865,11 @@ If you do not set ``needs_report_template``, the default template used is: - {{ link.get('allow_dead_links', False) | capitalize }} {% endfor %} {% endif %} - {# Output for needs_extra_links #} + {# Output for needs_links #} {# Output for needs_fields #} {% if fields|length != 0 %} - .. dropdown:: Need Extra Options + .. dropdown:: Need Fields :class: needs_report_table {% for field in fields %} @@ -902,7 +900,7 @@ If you do not set ``needs_report_template``, the default template used is: The plugin provides the following variables which you can use in your custom Jinja template: * types - list of :ref:`need types ` -* links - list of :ref:`needs_extra_links` +* links - list of :ref:`needs_links` * fields - list of :ref:`needs_fields` * usage - a dictionary object containing information about the following: + needs_amount -> total amount of need objects in the project @@ -2368,7 +2366,7 @@ Default value:: [ "field_success", - "extra_link_success", + "link_success", "select_success", "select_fail", "local_success", @@ -2390,8 +2388,8 @@ Available scenarios that can be ignored: - ``cfg_schema_error``: The user provided schema is invalid - ``field_success``: Global field validation was successful - ``field_fail``: Global field validation failed -- ``extra_link_success``: Global extra link validations was successful -- ``extra_link_fail``: Global extra link validation failed +- ``link_success``: Global extra link validations was successful +- ``link_fail``: Global extra link validation failed - ``select_success``: Successful select validation - ``select_fail``: Failed select validation - ``local_success``: Successful local validation @@ -2535,7 +2533,7 @@ needs_global_options .. deprecated:: 7.0.0 - Use :ref:`needs_fields` and :ref:`needs_extra_links` instead. + Use :ref:`needs_fields` and :ref:`needs_links` instead. .. versionchanged:: 5.1.0 @@ -2584,7 +2582,7 @@ This configuration allows for global defaults to be set for all needs, for any of the following fields: - any :ref:`needs_fields` key -- any ``needs_extra_links`` field +- any :ref:`needs_links` field - ``status`` - ``layout`` - ``style`` @@ -2615,7 +2613,7 @@ A match expression is a string, using Python syntax, that will be evaluated agai - ``is_external`` (``bool``) - ``is_import`` (``bool``) - :ref:`needs_fields` -- :ref:`needs_extra_links` (``tuple[str, ...]``) +- :ref:`needs_links` (``tuple[str, ...]``) - :ref:`needs_filter_data` If no predicates match, the ``default`` value is used (if present). @@ -2754,3 +2752,57 @@ Configuration example: .. code-block:: python needs_report_dead_links = False + +.. _`needs_extra_links`: + +needs_extra_links +~~~~~~~~~~~~~~~~~ + +.. deprecated:: 4.4.0 + Use :ref:`needs_links` instead. + +Allows the definition of additional link types as a list. + +Each configured link should define: + +- ``option``: The name of the option. Example "blocks". +- ``predicates`` (optional): A list of ``(match expression, value)`` tuples. + If specified, these will be evaluated in order for any need that does not explicitly set the field, with the first match setting the field value. +- ``default`` (optional): A default value for the field. + If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates. +- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options `. + Default: ``False``. +- ``incoming`` (optional): Incoming text, to use for incoming links. E.g. "is blocked by". +- ``outgoing`` (optional): Outgoing text, to use for outgoing links. E.g. "blocks". +- ``copy`` (optional): True/False. If True, the links will be copied also to the common link-list (link type ``links``). + Default: True +- ``allow_dead_links`` (optional): True/False. If True, dead links are allowed and do not throw a warning. + See :ref:`allow_dead_links` for details. Default: False. +- ``style`` (optional): A plantuml style description, e.g. "#FFCC00". Used for :ref:`needflow`. See :ref:`links_style`. +- ``style_part`` (optional): Same as ``style``, but get used if link is connected to a :ref:`need_part`. + See :ref:`links_style`. + +Configuration example: + +.. code-block:: python + + needs_extra_links = [ + { + "option": "checks", + }, + { + "option": "triggers", + "predicates": [ + ('"urgent" in tags', ["ID999", "ID998"]), + ], + "default": ["ID123"], + "incoming": "is triggered by", + "outgoing": "triggers", + "copy": False, + "allow_dead_links": True, + "style": "#00AA00", + "style_part": "#00AA00", + "style_start": "-", + "style_end": "--o", + } + ] diff --git a/docs/directives/need.rst b/docs/directives/need.rst index cc35dbda5..ecd02f7f5 100644 --- a/docs/directives/need.rst +++ b/docs/directives/need.rst @@ -126,30 +126,26 @@ You can easily set links to multiple needs by using **;** as a separator. This sets a link to id ``REQ_LINK_1``. -.. _need_extra_links: - extra links +++++++++++ -By using :ref:`needs_extra_links `, you can use the configured link-types to set additional **need** options. +By using :ref:`needs_links `, you can use the configured link-types to set additional **need** options. .. code-block:: python # conf.py - needs_extra_links = [ - { - "option": "blocks", + needs_links = { + "blocks": { "incoming": "is blocked by", "outgoing": "blocks" }, - { - "option": "tests", + "tests": { "incoming": "is tested by", "outgoing": "tests", "copy": False, "color": "#00AA00" } - ] + } .. need-example:: diff --git a/docs/directives/needflow.rst b/docs/directives/needflow.rst index 2f1032f0a..99d3f9050 100644 --- a/docs/directives/needflow.rst +++ b/docs/directives/needflow.rst @@ -201,7 +201,7 @@ show_link_names Adds the link type name beside connections. You can configure it globally by setting :ref:`needs_flow_show_links` in **conf.py**. -Setup data can be found in test case document ``tests/doc_test/doc_extra_links``. +Setup data can be found in test case document ``tests/doc_test/doc_links``. .. need-example:: @@ -243,7 +243,7 @@ You can avoid this by not setting **"links**" in the ``link_type`` option. You can set this option globally via the configuration option :ref:`needs_flow_link_types`. -See also :ref:`needs_extra_links` for more details about specific link types. +See also :ref:`needs_links` for more details about specific link types. .. need-example:: diff --git a/docs/directives/needreport.rst b/docs/directives/needreport.rst index c10421ac6..887c3b324 100644 --- a/docs/directives/needreport.rst +++ b/docs/directives/needreport.rst @@ -8,7 +8,7 @@ needreport **needreport** documents the following configurations from **conf.py**: * :ref:`Types ` -* :ref:`Links ` +* :ref:`Links ` * :ref:`Fields ` and it also adds some needs metrics using the `usage`_ option. @@ -59,7 +59,7 @@ The flag does not require any values. links ~~~~~ -Flag for adding information about the :ref:`needs_extra_links` configuration parameter. +Flag for adding information about the :ref:`needs_links` configuration parameter. The flag does not require any values. .. need-example:: diff --git a/docs/dynamic_functions.rst b/docs/dynamic_functions.rst index f5a085cfb..35f82c587 100644 --- a/docs/dynamic_functions.rst +++ b/docs/dynamic_functions.rst @@ -43,7 +43,7 @@ Dynamic functions can be used for the following directive options: - ``layout`` - ``constraints`` - :ref:`extra fields ` -- :ref:`needs_extra_links` +- :ref:`needs_links` .. deprecated:: 3.1.0 @@ -156,7 +156,7 @@ A variant definition can look like ``var_a:open`` or ``['name' in tags]:assigned .. important:: To use variants options, you must enable the feature for a need field by setting the - :ref:`needs_fields` or :ref:`needs_extra_links` configuration parameter's ``parse_variants`` option to ``True`` for the specific field. + :ref:`needs_fields` or :ref:`needs_links` configuration parameter's ``parse_variants`` option to ``True`` for the specific field. A variant definition has two parts: the **rule or key** and the **value**. For example, if we specify a variant definition as ``var_a:open``, then ``var_a`` is the key and ``open`` is the value. @@ -186,7 +186,7 @@ Rules for specifying variant definitions To implement variants options, you must configure the following in your ``conf.py`` file: -* :ref:`needs_fields` and/or :ref:`needs_extra_links` with the ``parse_variants`` option set to ``True`` for the specific field. +* :ref:`needs_fields` and/or :ref:`needs_links` with the ``parse_variants`` option set to ``True`` for the specific field. * :ref:`needs_variants` Use Cases diff --git a/docs/needs_templates/report_template.need b/docs/needs_templates/report_template.need index 9d933ef14..be62828bc 100644 --- a/docs/needs_templates/report_template.need +++ b/docs/needs_templates/report_template.need @@ -15,7 +15,7 @@ {# Output for needs_fields #} {% if options|length != 0 %} -.. dropdown:: Need Extra Options +.. dropdown:: Need Fields {% for option in options %} * {{ option }} @@ -23,10 +23,10 @@ {% endif %} {# Output for needs_fields #} -{# Output for needs_extra_links #} +{# Output for needs_links #} {% if links|length != 0 %} -.. dropdown:: Need Extra Links +.. dropdown:: Need Links {% for link in links %} * {{ link.option | upper }} @@ -36,5 +36,5 @@ + **:allow_dead_links:** {{ link.get('allow_dead_links', False) | capitalize }} {% endfor %} {% endif %} -{# Output for needs_extra_links #} +{# Output for needs_links #} diff --git a/docs/schema/index.rst b/docs/schema/index.rst index e3266ed42..9609b012b 100644 --- a/docs/schema/index.rst +++ b/docs/schema/index.rst @@ -18,7 +18,7 @@ See :ref:`migration_from_warnings_constraints` for details on how to migrate. that extends throughout the entire application. This includes: - **Early type validation**: All need fields are validated against their defined types, - with support for :ref:`field defaults `, :ref:`link defaults `, :ref:`dynamic_functions`, :ref:`variants `, + with support for :ref:`field defaults `, :ref:`link defaults `, :ref:`dynamic_functions`, :ref:`variants `, :ref:`needextend`. Needs that do not conform to their types are not created and lead to a warning. - **JSON export**: Generated :ref:`needs.json ` files honor the user provided @@ -70,9 +70,9 @@ The schema is configured in multiple places: schema constraints for that option, that will be checked for each need type. E.g. minimum/maximum for integers, enum for strings, etc. -- :ref:`needs_extra_links` is the place to add new link options that are then available +- :ref:`needs_links` is the place to add new link options that are then available for all need types. The type for link fields is pre-set to ``array[string]``, - as links are always lists of need IDs. The ``schema`` field in ``needs_extra_links`` + as links are always lists of need IDs. The ``schema`` field in ``needs_links`` supports setting global schema constraints for that link option, that will be checked for each need type. E.g. minimum/maximum number of links or need id patterns. The schema defined here always runs on unresolved local needs, i.e. links are a list of IDs. @@ -94,7 +94,7 @@ The schema is configured in multiple places: JSON schema might not complain about this. Therefore, Sphinx-Needs automatically injects the type information from ``needs_fields`` - and ``needs_extra_links`` (as well as core field definitions) into the + and ``needs_links`` (as well as core field definitions) into the :ref:`needs_schema_definitions` when type information is not explicitly provided in the schemas.json file. If type information is provided in schemas.json, it must match the definition from ``needs_fields`` or core fields. @@ -1149,8 +1149,8 @@ Local validation: - ``field_fail``: Field schema validation failed (local validation, defined via ``schema`` key in :ref:`needs_fields`) -- ``extra_link_fail``: Extra link schema validation failed - (defined via ``schema`` key in :ref:`needs_extra_links`) +- ``link_fail``: Extra link schema validation failed + (defined via ``schema`` key in :ref:`needs_links`) - ``local_fail``: Need-local validation failed (defined via ``validate.local`` in schemas of :ref:`needs_schema_definitions`) diff --git a/docs/services/index.rst b/docs/services/index.rst index d833f1db2..ac89854aa 100644 --- a/docs/services/index.rst +++ b/docs/services/index.rst @@ -28,7 +28,7 @@ Each service may have own options, which are used to configure the service itsel Please take a look into the related service documentation for information about them. You can always set all fields, which are also available for all need objects. -So the ones defined by :ref:`needs_fields` and :ref:`needs_extra_links`. +So the ones defined by :ref:`needs_fields` and :ref:`needs_links`. These options will then be set for all needs created by the requested service. Most services also support adding additional content to the created needs. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 67bca166e..18a81dd4e 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -95,7 +95,7 @@ that can be used to add additional data to the item or further style its represe To add additional fields to the directive, see :ref:`needs_fields`, and to add additional link types, - see :ref:`needs_extra_links`. + see :ref:`needs_links`. Enforcing valid need items .......................... @@ -130,18 +130,17 @@ Linking need items Now that we know how to create individual need items, the next thing we may want to do is to link them together. -We can define custom link types in the ``conf.py`` file, using the :ref:`needs_extra_links` configuration option: +We can define custom link types in the ``conf.py`` file, using the :ref:`needs_links` configuration option: .. code-block:: python - needs_extra_links = [ - { - "option": "tutorial_required_by", + needs_links = { + "tutorial_required_by": { "incoming": "requires", # text to describe incoming links "outgoing": "required by", # text to describe outgoing links "style": "#00AA00", # color for the link in diagrams }, - ] + } We can now uses these links when specifying need items, notice how "back links" are automatically generated when displaying the item: @@ -190,7 +189,7 @@ Lets also add some more need items to our plan: .. seealso:: - For full options see the reference sections for :ref:`need_extra_links configuration ` and :ref:`need items directive `. + For full options see the reference sections for :ref:`need_links configuration ` and :ref:`need items directive `. Importing need items -------------------- diff --git a/docs/ubproject.toml b/docs/ubproject.toml index 3cb0c7fbc..7af112a6b 100644 --- a/docs/ubproject.toml +++ b/docs/ubproject.toml @@ -222,8 +222,7 @@ prefix = "T_" color = "#f9e79f" style = "rectangle" -[[needs.extra_links]] -option = "blocks" +[needs.links.blocks] incoming = "is blocked by" outgoing = "blocks" copy = true @@ -233,24 +232,21 @@ style_start = "-" style_end = "-o" allow_dead_links = true -[[needs.extra_links]] -option = "tests" +[needs.links.tests] incoming = "is tested by" outgoing = "tests" copy = true style = "#00AA00" style_part = "dotted,#00AA00" -[[needs.extra_links]] -option = "checks" +[needs.links.checks] incoming = "is checked by" outgoing = "checks" copy = false style = "#00AA00" style_part = "dotted,#00AA00" -[[needs.extra_links]] -option = "triggers" +[needs.links.triggers] incoming = "triggered by" outgoing = "triggers" copy = false @@ -258,42 +254,37 @@ style = "#00AA00" style_part = "solid,#777777" allow_dead_links = true -[[needs.extra_links]] -option = "starts_with" +[needs.links.starts_with] incoming = "triggers directly" outgoing = "starts with" copy = false style = "#00AA00" style_part = "solid,#777777" -[[needs.extra_links]] -option = "starts_after" +[needs.links.starts_after] incoming = "triggers at end" outgoing = "starts after" copy = false style = "#00AA00" style_part = "solid,#777777" -[[needs.extra_links]] -option = "ends_with" +[needs.links.ends_with] incoming = "triggers to end with" outgoing = "ends with" copy = false style = "#00AA00" style_part = "solid,#777777" -[[needs.extra_links]] -option = "tutorial_required_by" +[needs.links.tutorial_required_by] incoming = "requires" outgoing = "required by" style = "#00AA00" -[[needs.extra_links]] -option = "tutorial_specifies" +[needs.links.tutorial_specifies] incoming = "specified by" outgoing = "specifies" -[[needs.extra_links]] +[needs.links.tutorial_tests] option = "tutorial_tests" incoming = "tested by" outgoing = "tests" diff --git a/sphinx_needs/api/configuration.py b/sphinx_needs/api/configuration.py index c9b4af6c5..69a6b8cba 100644 --- a/sphinx_needs/api/configuration.py +++ b/sphinx_needs/api/configuration.py @@ -175,7 +175,7 @@ def add_field( ) -# TODO(mh) add extra link api +# TODO(mh) add add_link api def add_dynamic_function( diff --git a/sphinx_needs/api/need.py b/sphinx_needs/api/need.py index aec936191..00a0691bf 100644 --- a/sphinx_needs/api/need.py +++ b/sphinx_needs/api/need.py @@ -113,7 +113,7 @@ def generate_need( :raises InvalidNeedException: If the data fails any validation issue. - ``kwargs`` can contain options defined in ``needs_fields`` and ``needs_extra_links``. + ``kwargs`` can contain options defined in ``needs_fields`` and ``needs_links``. If an entry is found in ``kwargs``, which *is not* specified in the configuration or registered e.g. via ``add_field``, an exception is raised. @@ -185,7 +185,7 @@ def generate_need( if unknown_kwargs: raise InvalidNeedException( "invalid_kwargs", - f"{unknown_kwargs!r} not in 'needs_fields' or 'needs_extra_links.", + f"{unknown_kwargs!r} not in 'needs_fields' or 'needs_links'.", ) # get the need type data @@ -600,7 +600,7 @@ def add_need( ``add_need`` allows to create needs programmatically and use its returned node to be integrated in any docutils based structure. - ``kwargs`` can contain options defined in ``needs_fields`` and ``needs_extra_links``. + ``kwargs`` can contain options defined in ``needs_fields`` and ``needs_links``. If an entry is found in ``kwargs``, which *is not* specified in the configuration or registered e.g. via ``add_field``, an exception is raised. diff --git a/sphinx_needs/config.py b/sphinx_needs/config.py index c01f81b9b..3d03108ec 100644 --- a/sphinx_needs/config.py +++ b/sphinx_needs/config.py @@ -12,13 +12,13 @@ from sphinx_needs.defaults import DEFAULT_DIAGRAM_TEMPLATE from sphinx_needs.logging import get_logger, log_warning from sphinx_needs.schema.config import ( - ExtraLinkSchemaType, FieldBooleanSchemaType, FieldIntegerSchemaType, FieldMultiValueSchemaType, FieldNumberSchemaType, FieldSchemaTypes, FieldStringSchemaType, + LinkSchemaType, SchemasFileRootType, ) @@ -261,15 +261,15 @@ class ExternalSource(TypedDict, total=False): """ -class LinkOptionsType(TypedDict, total=False): - """Options for links between needs""" +class NeedLinksConfig(TypedDict, total=False): + """Defines a link type for needs (used in needs_links dict)""" - option: Required[str] - """The name of the link option""" + description: NotRequired[str] + """A description of the link type.""" incoming: str - """The incoming link title""" + """The incoming link title. Default: ' incoming'""" outgoing: str - """The outgoing link title""" + """The outgoing link title. Default: ''""" copy: bool """Copy to common links data. Default: False""" color: str @@ -284,7 +284,7 @@ class LinkOptionsType(TypedDict, total=False): """Used for needflow. Default: '->'""" allow_dead_links: bool """If True, add a 'forbidden' class to dead links""" - schema: NotRequired[ExtraLinkSchemaType] + schema: NotRequired[LinkSchemaType] """ A JSON schema for the link option. @@ -300,6 +300,13 @@ class LinkOptionsType(TypedDict, total=False): """Whether variants are parsed in this field.""" +class LinkOptionsType(NeedLinksConfig): + """Options for links between needs (deprecated, use NeedLinksConfig instead)""" + + option: Required[str] + """The name of the link""" + + class NeedType(TypedDict): """Defines a need type""" @@ -520,7 +527,7 @@ def get_default(cls, name: str) -> Any: schema_debug_ignore: list[str] = field( default_factory=lambda: [ "field_success", - "extra_link_success", + "link_success", "select_success", "select_fail", "local_success", @@ -699,10 +706,14 @@ def functions(self) -> Mapping[str, NeedFunctionsType]: default="→\xa0", metadata={"rebuild": "html", "types": (str,)} ) """Prefix for need_part output in tables""" + _links: dict[str, NeedLinksConfig] = field( + default_factory=dict, metadata={"rebuild": "html", "types": (dict,)} + ) + """Dict of additional link types between needs (name -> config).""" _extra_links: list[LinkOptionsType] = field( default_factory=list, metadata={"rebuild": "html", "types": ()} ) - """List of additional link types between needs (internal config, use schema for access after config resolution)""" + """DEPRECATED: List of additional link types between needs. Use needs_links instead.""" report_dead_links: bool = field( default=True, metadata={"rebuild": "html", "types": (bool,)} ) diff --git a/sphinx_needs/directives/needgantt.py b/sphinx_needs/directives/needgantt.py index 3c7c14c6d..61a5d3d45 100644 --- a/sphinx_needs/directives/needgantt.py +++ b/sphinx_needs/directives/needgantt.py @@ -158,8 +158,7 @@ def get_link_type_option(self, name: str, default: str = "") -> list[str]: continue if link_type not in conf_link_types_name: raise SphinxNeedsLinkTypeException( - link_type - + "does not exist in configuration option needs_extra_links" + link_type + " does not exist in configuration option needs_links" ) final_link_types.append(link_type) @@ -176,9 +175,6 @@ def process_needgantt( env = app.env needs_config = NeedsSphinxConfig(app.config) - # link_types = needs_config.extra_links - # allowed_link_types_options = [link.upper() for link in needs_config.flow_link_types] - # NEEDGANTT # for node in doctree.findall(Needgantt): for node in found_nodes: diff --git a/sphinx_needs/directives/needimport_template.rst b/sphinx_needs/directives/needimport_template.rst index 3f829ff5f..d42ff2ff0 100644 --- a/sphinx_needs/directives/needimport_template.rst +++ b/sphinx_needs/directives/needimport_template.rst @@ -23,19 +23,19 @@ {% endif %} {# Output for needs_types #} -{# Output for needs_extra_links #} +{# Output for needs_links #} {% if links|length != 0 %} .. container:: toggle needs_report_table .. container:: header - **Need Extra Links** + **Need Links** .. list-table:: :widths: 10 30 30 5 20 :header-rows: 1 - * - OPTION + * - NAME - INCOMING - OUTGOING - COPY @@ -48,7 +48,7 @@ - {{ link.get('allow_dead_links', False) | capitalize }} {% endfor %} {% endif %} -{# Output for needs_extra_links #} +{# Output for needs_links #} {# Output for needs_fields #} {% if options|length != 0 %} @@ -56,7 +56,7 @@ .. container:: header - **Need Extra Options** + **Need Fields** {% for option in options %} * {{ option }} diff --git a/sphinx_needs/directives/needreport_template.rst b/sphinx_needs/directives/needreport_template.rst index cc7da1f0f..6fc805743 100644 --- a/sphinx_needs/directives/needreport_template.rst +++ b/sphinx_needs/directives/needreport_template.rst @@ -20,16 +20,16 @@ {% endif %} {# Output for needs_types #} -{# Output for needs_extra_links #} +{# Output for needs_links #} {% if links|length != 0 %} -.. {{ report_directive }}:: Need Extra Links +.. {{ report_directive }}:: Need Links .. list-table:: :widths: 10 30 30 5 20 :header-rows: 1 - * - OPTION + * - NAME - INCOMING - OUTGOING - COPY @@ -42,12 +42,12 @@ - {{ link.get('allow_dead_links', False) | capitalize }} {% endfor %} {% endif %} -{# Output for needs_extra_links #} +{# Output for needs_links #} {# Output for needs_fields #} {% if options|length != 0 %} -.. {{ report_directive }}:: Need Extra Options +.. {{ report_directive }}:: Need Fields {% for option in options %} * {{ option }} diff --git a/sphinx_needs/directives/needservice.py b/sphinx_needs/directives/needservice.py index 5d6a4cb4e..720fd8948 100644 --- a/sphinx_needs/directives/needservice.py +++ b/sphinx_needs/directives/needservice.py @@ -118,7 +118,7 @@ def run(self) -> Sequence[nodes.Node]: del datum["title"] # We need to check if all given options from services are really available as configured - # needs_fields or needs_extra_links + # needs_fields or needs_links missing_options = {} for element in datum: if element not in defined_options: diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py index 3e3cb6503..7a20bae67 100644 --- a/sphinx_needs/directives/needuml.py +++ b/sphinx_needs/directives/needuml.py @@ -449,7 +449,7 @@ def imports(self, *args: str) -> str: raise NeedumlException( "Jinja function 'import()' is not supported in needuml directive." ) - # gets all need ids from need links/extra_links options and wrap into jinja function uml() + # gets all need ids from need links and wrap into jinja function uml() need_info = self.needs[self.parent_need_id] uml_ids = [] for option_name in args: diff --git a/sphinx_needs/layout.py b/sphinx_needs/layout.py index 2ddd03cb4..82b8f3c8c 100644 --- a/sphinx_needs/layout.py +++ b/sphinx_needs/layout.py @@ -625,7 +625,7 @@ def meta_all( :param prefix: :param postfix: :param exclude: List of value names, which are excluded from output - :param no_links: excludes all incoming and outgoing extra link types from output + :param no_links: excludes all incoming and outgoing link types from output :param defaults: If True, default excludes are added. This filters out all internal data, which is normally not relevant for the user. :param show_empty: If true, also need data with no value will be printed. Mostly useful for debugging. @@ -699,7 +699,7 @@ def meta_links_all( :param prefix: prefix string :param postfix: postfix string - :param exclude: list of extra link type names, which are excluded from output + :param exclude: list of link type names, which are excluded from output :return: docutils nodes """ exclude = exclude or [] diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py index 349975658..d6ec6dd59 100644 --- a/sphinx_needs/needs.py +++ b/sphinx_needs/needs.py @@ -4,6 +4,7 @@ import json from collections.abc import Callable from copy import deepcopy +from itertools import chain from pathlib import Path from timeit import default_timer as timer # Used for timing measurements from typing import Any, TypedDict, cast @@ -30,6 +31,7 @@ from sphinx_needs.config import ( _NEEDS_CONFIG, LinkOptionsType, + NeedLinksConfig, NeedsSphinxConfig, ) from sphinx_needs.data import ( @@ -624,6 +626,22 @@ def load_config(app: Sphinx, *_args: Any) -> None: None, ) + # Process needs_extra_links (deprecated list-based format) + if not isinstance(needs_config._extra_links, list): + raise NeedsConfigException("Config option 'needs_extra_links' must be a list.") + + if needs_config._extra_links: + log_warning( + LOGGER, + 'Config option "needs_extra_links" is deprecated. Please use "needs_links" instead.', + "deprecated", + None, + ) + + # Process needs_links (new dict-based format) + if not isinstance(needs_config._links, dict): + raise NeedsConfigException("Config option 'needs_links' must be a dict.") + load_schemas_config_from_json(app, app.config) @@ -693,47 +711,31 @@ def merge_default_configs(_app: Sphinx, config: Config) -> None: for needs_func in needs_config._functions: _NEEDS_CONFIG.add_function(needs_func) - # The default link name. Must exist in all configurations. Therefore we set it here - # for the user. - common_links: list[LinkOptionsType] = [] - link_types = needs_config._extra_links - basic_link_type_found = False - parent_needs_link_type_found = False - for link_type in link_types: - if link_type["option"] == "links": - basic_link_type_found = True - elif link_type["option"] == "parent_needs": - parent_needs_link_type_found = True - - if not basic_link_type_found: - common_links.append( - { - "option": "links", - "outgoing": "links outgoing", - "incoming": "links incoming", - "copy": False, - "color": "#000000", - } - ) - - if not parent_needs_link_type_found: - common_links.append( - { - "option": "parent_needs", - "outgoing": "parent needs", - "incoming": "child needs", - "copy": False, - "color": "#333333", - } - ) - - needs_config._extra_links = common_links + needs_config._extra_links + # The default link name. Must exist in all configurations. Therefore we set it here for the user. + if "links" not in needs_config._links: + needs_config._links["links"] = { + "outgoing": "links outgoing", + "incoming": "links incoming", + "copy": False, + "color": "#000000", + } + if "parent_needs" not in needs_config._links: + needs_config._links["parent_needs"] = { + "outgoing": "parent needs", + "incoming": "child needs", + "copy": False, + "color": "#333333", + } - for link in needs_config._extra_links: + # Ensure all links have outgoing and incoming defined, so that we can rely on it later on. + for name, link in chain( + needs_config._links.items(), + ((v["option"], v) for v in needs_config._extra_links), + ): if "outgoing" not in link: - link["outgoing"] = link["option"] + link["outgoing"] = name if "incoming" not in link: - link["incoming"] = f"{link['option']} incoming" + link["incoming"] = f"{name} incoming" def check_configuration(app: Sphinx, config: Config) -> None: @@ -769,7 +771,7 @@ def check_configuration(app: Sphinx, config: Config) -> None: if internal in link_types: raise NeedsConfigException( f'Link type name "{internal}" already used internally. ' - " Please use another name in your config (needs_extra_links)." + " Please use another name in your config (needs_links)." ) # Check if option and link are using the same name @@ -976,8 +978,15 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N except Exception as exc: raise NeedsConfigException(f"Invalid field {name!r}: {exc}") from exc - for link in needs_config._extra_links: - name = link["option"] + # Get set of link names from needs_links (new config) vs needs_extra_links (deprecated) + links: dict[str, tuple[LinkOptionsType | NeedLinksConfig, str]] = { + k: (v, "needs_links") for k, v in needs_config._links.items() + } | { + link["option"]: (link, "needs_extra_links") + for link in needs_config._extra_links + } + + for name, (link, config_source) in links.items(): try: # create link schema, with defaults if not defined _schema = ( @@ -1007,7 +1016,7 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N display_config = LinkDisplayConfig(**display_kwargs) link_field = LinkSchema( name=name, - description="Link field", # TODO allow this to be set by the user + description=link.get("description", "Link field"), schema=_schema, # type: ignore[arg-type] default=LinksLiteralValue([]), allow_defaults=True, @@ -1023,24 +1032,24 @@ def create_schema(app: Sphinx, env: BuildEnvironment, _docnames: list[str]) -> N _set_default_on_field( link_field, link["default"], - "needs_extra_links", + config_source, allow_coercion=True, ) if "predicates" in link: _set_predicates_on_field( link_field, link["predicates"], - "needs_extra_links", + config_source, allow_coercion=True, ) schema.add_link_field(link_field) except Exception as exc: - raise NeedsConfigException(f"Invalid extra link {name!r}: {exc}") from exc + raise NeedsConfigException(f"Invalid link {name!r}: {exc}") from exc if needs_config._global_options: log_warning( LOGGER, - 'Config option "needs_global_options" is deprecated. Please use needs_fields and needs_extra_links instead.', + 'Config option "needs_global_options" is deprecated. Please use needs_fields and needs_links instead.', "deprecated", None, ) diff --git a/sphinx_needs/needs_schema.py b/sphinx_needs/needs_schema.py index a174c2d29..d6433fb43 100644 --- a/sphinx_needs/needs_schema.py +++ b/sphinx_needs/needs_schema.py @@ -11,13 +11,13 @@ from sphinx_needs.config import NeedFields from sphinx_needs.exceptions import VariantParsingException from sphinx_needs.schema.config import ( - ExtraLinkSchemaType, FieldBooleanSchemaType, FieldIntegerSchemaType, FieldMultiValueSchemaType, FieldNumberSchemaType, FieldSchemaTypes, FieldStringSchemaType, + LinkSchemaType, validate_field_schema, validate_link_schema_type, ) @@ -543,7 +543,7 @@ class LinkSchema: name: str description: str = "" - schema: ExtraLinkSchemaType + schema: LinkSchemaType directive_option: bool = False allow_extend: bool = False parse_dynamic_functions: bool = False diff --git a/sphinx_needs/needsfile.py b/sphinx_needs/needsfile.py index 7fa40527a..514144381 100644 --- a/sphinx_needs/needsfile.py +++ b/sphinx_needs/needsfile.py @@ -36,7 +36,7 @@ def generate_needs_schema( * the core fields defined in NeedsCoreFields * the extra fields defined dynamically * the global options defined dynamically - * the extra links defined dynamically + * the links defined dynamically """ properties: dict[str, Any] = {} diff --git a/sphinx_needs/schema/config.py b/sphinx_needs/schema/config.py index 27cae9d73..d242a8f57 100644 --- a/sphinx_needs/schema/config.py +++ b/sphinx_needs/schema/config.py @@ -306,11 +306,11 @@ def validate_field_schema(data: Any) -> FieldSchemaTypes: return validator(data) -class ExtraLinkItemSchemaType(TypedDict): +class LinkItemSchemaType(TypedDict): """Items in array of link string ids""" type: Literal["string"] - """Extra link string type, can only be string and is injected automatically.""" + """Link string type, can only be string and is injected automatically.""" minLength: NotRequired[int] """Minimum string length of each outgoing link id.""" maxLength: NotRequired[int] @@ -319,18 +319,18 @@ class ExtraLinkItemSchemaType(TypedDict): """A regex pattern to validate against.""" -class ExtraLinkSchemaType(TypedDict): +class LinkSchemaType(TypedDict): """Defines a schema for unresolved need extra string links.""" type: Literal["array"] - """Type for extra links, can only be array and is injected automatically.""" - items: NotRequired[ExtraLinkItemSchemaType] + """Type for links, can only be array and is injected automatically.""" + items: NotRequired[LinkItemSchemaType] """Schema constraints that applies to all items in the need id string list.""" minItems: NotRequired[int] """Minimum number of items in the array (outgoing link ids).""" maxItems: NotRequired[int] """Maximum number of items in the array (outgoing link ids).""" - contains: NotRequired[ExtraLinkItemSchemaType] + contains: NotRequired[LinkItemSchemaType] """Schema constraints that must be contained in the need id string list.""" minContains: NotRequired[int] """Minimum number of contains items in the array (outgoing link ids).""" @@ -338,12 +338,12 @@ class ExtraLinkSchemaType(TypedDict): """Maximum number of contains items in the array (outgoing link ids).""" -def validate_link_schema_type(data: Any) -> ExtraLinkSchemaType: - """Validate that the given data is an ExtraLinkSchemaType. +def validate_link_schema_type(data: Any) -> LinkSchemaType: + """Validate that the given data is an LinkSchemaType. :raises TypeError: if the data is not valid. """ - schema = _get_schema(ExtraLinkSchemaType) + schema = _get_schema(LinkSchemaType) try: schema.validate(data) except jsonschema_rs.ValidationError as e: @@ -352,10 +352,10 @@ def validate_link_schema_type(data: Any) -> ExtraLinkSchemaType: for field in ("minContains", "maxContains"): if field in data: raise TypeError(f"'{field}' defined, but 'contains' is missing.") - return cast(ExtraLinkSchemaType, data) + return cast(LinkSchemaType, data) -FieldAndLinkSchemaTypes = FieldSchemaTypes | ExtraLinkSchemaType +FieldAndLinkSchemaTypes = FieldSchemaTypes | LinkSchemaType """Union type for all field and link schemas.""" @@ -364,7 +364,7 @@ class NeedFieldsSchemaType(AllOfSchemaType): Schema for a set of need fields of all schema types. This includes single value fields, multi-value fields, - and unresolved extra links. + and unresolved links. Intented to validate multiple fields on a need type. """ @@ -390,10 +390,10 @@ class MessageRuleEnum(str, Enum): """Global field validation was successful.""" field_fail = "field_fail" """Global field validation failed.""" - extra_link_success = "extra_link_success" - """Global extra link validation was successful.""" - extra_link_fail = "extra_link_fail" - """Global extra link validation failed.""" + link_success = "link_success" + """Global link validation was successful.""" + link_fail = "link_fail" + """Global link validation failed.""" select_success = "select_success" """ Need validates against the select schema. @@ -482,8 +482,8 @@ class SeverityEnum(IntEnum): MessageRuleEnum.cfg_schema_error: SeverityEnum.config_error, MessageRuleEnum.field_success: SeverityEnum.none, MessageRuleEnum.field_fail: SeverityEnum.violation, # cannot be changed by user - MessageRuleEnum.extra_link_success: SeverityEnum.none, - MessageRuleEnum.extra_link_fail: SeverityEnum.violation, # cannot be changed by user + MessageRuleEnum.link_success: SeverityEnum.none, + MessageRuleEnum.link_fail: SeverityEnum.violation, # cannot be changed by user MessageRuleEnum.select_success: SeverityEnum.none, MessageRuleEnum.select_fail: SeverityEnum.none, MessageRuleEnum.local_success: SeverityEnum.none, @@ -502,7 +502,7 @@ class SeverityEnum(IntEnum): Default severity for each rule. User provided schemas can overwrite the severity of a rule. -The rules ``field_fail`` and ``extra_link_fail`` cannot be changed by the user, +The rules ``field_fail`` and ``link_fail`` cannot be changed by the user, but they can be suppressed specifically using suppress_warnings config. """ diff --git a/sphinx_needs/schema/config_utils.py b/sphinx_needs/schema/config_utils.py index 5d204a990..1f5f025cd 100644 --- a/sphinx_needs/schema/config_utils.py +++ b/sphinx_needs/schema/config_utils.py @@ -116,8 +116,8 @@ def resolve_schemas_config( ) from exc # after type check, we can safely walk the schema for nested checks; - # check if network links are defined as extra links - check_network_links_against_extra_links( + # check if network links are defined as links + check_network_links_against_links( needs_config.schema_definitions["schemas"], fields_schema, ) @@ -171,10 +171,10 @@ def validate_severity(schemas: list[SchemasRootType]) -> None: ) -def check_network_links_against_extra_links( +def check_network_links_against_links( schemas: list[SchemasRootType], fields_schema: FieldsSchema ) -> None: - """Check if network links are defined as extra links.""" + """Check if network links are defined as links.""" for schema in schemas: validate_schemas: list[ValidateSchemaType] = [schema["validate"]] while validate_schemas: diff --git a/sphinx_needs/schema/core.py b/sphinx_needs/schema/core.py index 73dd02d9f..2982a5eed 100644 --- a/sphinx_needs/schema/core.py +++ b/sphinx_needs/schema/core.py @@ -57,8 +57,8 @@ def validate_field_link_schemas( For each need, only the properties present in the schema are included (excluding ``None`` values), so the validator only sees relevant fields. - Errors on link properties use :attr:`MessageRuleEnum.extra_link_fail` and - the ``extra_links > schema`` schema path prefix, while errors on other + Errors on link properties use :attr:`MessageRuleEnum.link_fail` and + the ``links > schema`` schema path prefix, while errors on other properties use :attr:`MessageRuleEnum.field_fail` and ``fields > schema``. :param config: The Sphinx-Needs configuration. @@ -120,12 +120,8 @@ def validate_field_link_schemas( # the root property name from the instance path root_prop = str(err.instance_path[0]) if err.instance_path else None is_link = root_prop is not None and root_prop in link_names - rule = ( - MessageRuleEnum.extra_link_fail - if is_link - else MessageRuleEnum.field_fail - ) - schema_prefix = "extra_links" if is_link else "fields" + rule = MessageRuleEnum.link_fail if is_link else MessageRuleEnum.field_fail + schema_prefix = "links" if is_link else "fields" warning: OntologyWarning = { "rule": rule, "severity": get_severity(rule), diff --git a/sphinx_needs/schema/jsons/ExtraLinkItemSchemaType.schema.json b/sphinx_needs/schema/jsons/LinkItemSchemaType.schema.json similarity index 91% rename from sphinx_needs/schema/jsons/ExtraLinkItemSchemaType.schema.json rename to sphinx_needs/schema/jsons/LinkItemSchemaType.schema.json index 455d48dad..1a998ecde 100644 --- a/sphinx_needs/schema/jsons/ExtraLinkItemSchemaType.schema.json +++ b/sphinx_needs/schema/jsons/LinkItemSchemaType.schema.json @@ -23,6 +23,6 @@ "required": [ "type" ], - "title": "ExtraLinkItemSchemaTypeModel", + "title": "LinkItemSchemaTypeModel", "type": "object" } diff --git a/sphinx_needs/schema/jsons/ExtraLinkSchemaType.schema.json b/sphinx_needs/schema/jsons/LinkSchemaType.schema.json similarity index 85% rename from sphinx_needs/schema/jsons/ExtraLinkSchemaType.schema.json rename to sphinx_needs/schema/jsons/LinkSchemaType.schema.json index 369d5f018..9b7a0de59 100644 --- a/sphinx_needs/schema/jsons/ExtraLinkSchemaType.schema.json +++ b/sphinx_needs/schema/jsons/LinkSchemaType.schema.json @@ -1,6 +1,6 @@ { "$defs": { - "ExtraLinkItemSchemaType": { + "LinkItemSchemaType": { "additionalProperties": false, "description": "Items in array of link string ids", "properties": { @@ -25,7 +25,7 @@ "required": [ "type" ], - "title": "ExtraLinkItemSchemaType", + "title": "LinkItemSchemaType", "type": "object" } }, @@ -33,10 +33,10 @@ "additionalProperties": false, "properties": { "contains": { - "$ref": "#/$defs/ExtraLinkItemSchemaType" + "$ref": "#/$defs/LinkItemSchemaType" }, "items": { - "$ref": "#/$defs/ExtraLinkItemSchemaType" + "$ref": "#/$defs/LinkItemSchemaType" }, "maxContains": { "title": "Maxcontains", @@ -63,6 +63,6 @@ "required": [ "type" ], - "title": "ExtraLinkSchemaTypeModel", + "title": "LinkSchemaTypeModel", "type": "object" } diff --git a/sphinx_needs/schema/jsons/SchemasRootType.schema.json b/sphinx_needs/schema/jsons/SchemasRootType.schema.json index 5fe1960ba..a7e362299 100644 --- a/sphinx_needs/schema/jsons/SchemasRootType.schema.json +++ b/sphinx_needs/schema/jsons/SchemasRootType.schema.json @@ -21,72 +21,6 @@ "title": "AllOfSchemaType", "type": "object" }, - "ExtraLinkItemSchemaType": { - "additionalProperties": false, - "description": "Items in array of link string ids", - "properties": { - "maxLength": { - "title": "Maxlength", - "type": "integer" - }, - "minLength": { - "title": "Minlength", - "type": "integer" - }, - "pattern": { - "title": "Pattern", - "type": "string" - }, - "type": { - "const": "string", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "ExtraLinkItemSchemaType", - "type": "object" - }, - "ExtraLinkSchemaType": { - "additionalProperties": false, - "description": "Defines a schema for unresolved need extra string links.", - "properties": { - "contains": { - "$ref": "#/$defs/ExtraLinkItemSchemaType" - }, - "items": { - "$ref": "#/$defs/ExtraLinkItemSchemaType" - }, - "maxContains": { - "title": "Maxcontains", - "type": "integer" - }, - "maxItems": { - "title": "Maxitems", - "type": "integer" - }, - "minContains": { - "title": "Mincontains", - "type": "integer" - }, - "minItems": { - "title": "Minitems", - "type": "integer" - }, - "type": { - "const": "array", - "title": "Type", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "ExtraLinkSchemaType", - "type": "object" - }, "FieldBooleanSchemaType": { "additionalProperties": false, "description": "Boolean field schema.\n\nA predefined set of truthy/falsy strings are accepted:\n- truthy = {\"true\", \"yes\", \"y\", \"on\", \"1\"}\n- falsy = {\"false\", \"no\", \"n\", \"off\", \"0\"}\n\nFor fixed values, the const field can be used.\nenum is not supported as const is functionally equivalent and less verbose.", @@ -324,9 +258,75 @@ "title": "FieldStringSchemaType", "type": "object" }, + "LinkItemSchemaType": { + "additionalProperties": false, + "description": "Items in array of link string ids", + "properties": { + "maxLength": { + "title": "Maxlength", + "type": "integer" + }, + "minLength": { + "title": "Minlength", + "type": "integer" + }, + "pattern": { + "title": "Pattern", + "type": "string" + }, + "type": { + "const": "string", + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "LinkItemSchemaType", + "type": "object" + }, + "LinkSchemaType": { + "additionalProperties": false, + "description": "Defines a schema for unresolved need extra string links.", + "properties": { + "contains": { + "$ref": "#/$defs/LinkItemSchemaType" + }, + "items": { + "$ref": "#/$defs/LinkItemSchemaType" + }, + "maxContains": { + "title": "Maxcontains", + "type": "integer" + }, + "maxItems": { + "title": "Maxitems", + "type": "integer" + }, + "minContains": { + "title": "Mincontains", + "type": "integer" + }, + "minItems": { + "title": "Minitems", + "type": "integer" + }, + "type": { + "const": "array", + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "LinkSchemaType", + "type": "object" + }, "NeedFieldsSchemaType": { "additionalProperties": false, - "description": "Schema for a set of need fields of all schema types.\n\nThis includes single value fields, multi-value fields,\nand unresolved extra links.\n\nIntented to validate multiple fields on a need type.", + "description": "Schema for a set of need fields of all schema types.\n\nThis includes single value fields, multi-value fields,\nand unresolved links.\n\nIntented to validate multiple fields on a need type.", "properties": { "allOf": { "items": { @@ -361,7 +361,7 @@ "$ref": "#/$defs/FieldMultiValueSchemaType" }, { - "$ref": "#/$defs/ExtraLinkSchemaType" + "$ref": "#/$defs/LinkSchemaType" } ] }, diff --git a/tests/doc_test/doc_field_defaults/conf.py b/tests/doc_test/doc_field_defaults/conf.py index ca068caa3..a612285d3 100644 --- a/tests/doc_test/doc_field_defaults/conf.py +++ b/tests/doc_test/doc_field_defaults/conf.py @@ -8,15 +8,13 @@ } ] -needs_extra_links = [ - { - "option": "link1", +needs_links = { + "link1": { "incoming": "is linked by", "outgoing": "links to", "default": ["SPEC_1"], }, - { - "option": "link2", + "link2": { "incoming": "is linked by", "outgoing": "links to", "predicates": [ @@ -25,13 +23,12 @@ ], "default": ["SPEC_1"], }, - { - "option": "link3", + "link3": { "incoming": "is linked by", "outgoing": "links to", "default": 1, }, -] +} needs_fields = { "tags": { diff --git a/tests/doc_test/doc_global_options/conf.py b/tests/doc_test/doc_global_options/conf.py index 15057219c..a885836ef 100644 --- a/tests/doc_test/doc_global_options/conf.py +++ b/tests/doc_test/doc_global_options/conf.py @@ -8,23 +8,20 @@ } ] -needs_extra_links = [ - { - "option": "link1", +needs_links = { + "link1": { "incoming": "is linked by", "outgoing": "links to", }, - { - "option": "link2", + "link2": { "incoming": "is linked by", "outgoing": "links to", }, - { - "option": "link3", + "link3": { "incoming": "is linked by", "outgoing": "links to", }, -] +} needs_fields = { "option_1": {"nullable": True}, diff --git a/tests/doc_test/doc_extra_links/conf.py b/tests/doc_test/doc_links/conf.py similarity index 94% rename from tests/doc_test/doc_extra_links/conf.py rename to tests/doc_test/doc_links/conf.py index 7cfe98539..832954489 100644 --- a/tests/doc_test/doc_extra_links/conf.py +++ b/tests/doc_test/doc_links/conf.py @@ -36,21 +36,22 @@ }, ] -needs_extra_links = [ - { - "option": "links", +needs_links = { + "links": { "copy": False, "style": "#black", "style_part": "dotted,#black", }, - { - "option": "blocks", + "blocks": { "incoming": "is blocked by", "outgoing": "blocks", "copy": True, "style": "bold,#AA0000", "allow_dead_links": True, }, +} + +needs_extra_links = [ { "option": "tests", "incoming": "is tested by", @@ -58,7 +59,7 @@ "copy": False, "style": "dashed,#00AA00", "style_part": "dotted,#00AA00", - }, + } ] needs_flow_link_types = ["links", "tests"] diff --git a/tests/doc_test/doc_extra_links/index.rst b/tests/doc_test/doc_links/index.rst similarity index 100% rename from tests/doc_test/doc_extra_links/index.rst rename to tests/doc_test/doc_links/index.rst diff --git a/tests/doc_test/doc_list2need/conf.py b/tests/doc_test/doc_list2need/conf.py index 43d93e5f4..0e806ff19 100644 --- a/tests/doc_test/doc_list2need/conf.py +++ b/tests/doc_test/doc_list2need/conf.py @@ -38,7 +38,7 @@ }, ] -needs_extra_links = [ - {"option": "checks", "incoming": "is checked by", "outgoing": "checks"}, - {"option": "triggers", "incoming": "is triggered by", "outgoing": "triggers"}, -] +needs_links = { + "checks": {"incoming": "is checked by", "outgoing": "checks"}, + "triggers": {"incoming": "is triggered by", "outgoing": "triggers"}, +} diff --git a/tests/doc_test/doc_need_parts/conf.py b/tests/doc_test/doc_need_parts/conf.py index 163a87c54..0c2823d8c 100644 --- a/tests/doc_test/doc_need_parts/conf.py +++ b/tests/doc_test/doc_need_parts/conf.py @@ -31,18 +31,16 @@ }, ] -needs_extra_links = [ - { - "option": "other_links", +needs_links = { + "other_links": { "incoming": "is incoming", "outgoing": "is outgoing", }, - { - "option": "more_links", + "more_links": { "incoming": "is incoming", "outgoing": "is outgoing", }, -] +} needs_build_json = True needs_json_remove_defaults = True diff --git a/tests/doc_test/doc_needarch_jinja_func_import/conf.py b/tests/doc_test/doc_needarch_jinja_func_import/conf.py index f20c68b8c..cced6c9cf 100644 --- a/tests/doc_test/doc_needarch_jinja_func_import/conf.py +++ b/tests/doc_test/doc_needarch_jinja_func_import/conf.py @@ -66,16 +66,14 @@ }, ] -needs_extra_links = [ - { - "option": "uses", +needs_links = { + "uses": { "incoming": "is used by", "outgoing": "uses", }, - { - "option": "tests", + "tests": { "incoming": "is tested by", "outgoing": "tests", "style": "#00AA00", }, -] +} diff --git a/tests/doc_test/doc_needflow_incl_child_needs/conf.py b/tests/doc_test/doc_needflow_incl_child_needs/conf.py index 96753e0bd..aab24df0d 100644 --- a/tests/doc_test/doc_needflow_incl_child_needs/conf.py +++ b/tests/doc_test/doc_needflow_incl_child_needs/conf.py @@ -37,18 +37,3 @@ "style": "node", }, ] - - -# needs_extra_links = [ -# { -# "option": "blocks", -# "incoming": "is blocked by", -# "outgoing": "blocks", -# "copy": False, -# "style": "#AA0000", -# "style_part": "dotted,#AA0000", -# "style_start": "-", -# "style_end": "-o", -# "allow_dead_links": True, -# }, -# ] diff --git a/tests/doc_test/doc_needs_filter_func_allow_dirty_filter/conf.py b/tests/doc_test/doc_needs_filter_func_allow_dirty_filter/conf.py index a70677d7f..4de6b6a3c 100644 --- a/tests/doc_test/doc_needs_filter_func_allow_dirty_filter/conf.py +++ b/tests/doc_test/doc_needs_filter_func_allow_dirty_filter/conf.py @@ -26,15 +26,14 @@ needs_fields = {"ti": {"nullable": True}, "tcl": {"nullable": True}} -needs_extra_links = [ - { - "option": "features", +needs_links = { + "features": { "incoming": "featured by", "outgoing": "features", "copy": False, "style": "#Gold", "style_part": "#Gold", }, -] +} needs_allow_unsafe_filters = True diff --git a/tests/doc_test/doc_needuml_jinja_func_import_negative_tests/conf.py b/tests/doc_test/doc_needuml_jinja_func_import_negative_tests/conf.py index f20c68b8c..cced6c9cf 100644 --- a/tests/doc_test/doc_needuml_jinja_func_import_negative_tests/conf.py +++ b/tests/doc_test/doc_needuml_jinja_func_import_negative_tests/conf.py @@ -66,16 +66,14 @@ }, ] -needs_extra_links = [ - { - "option": "uses", +needs_links = { + "uses": { "incoming": "is used by", "outgoing": "uses", }, - { - "option": "tests", + "tests": { "incoming": "is tested by", "outgoing": "tests", "style": "#00AA00", }, -] +} diff --git a/tests/doc_test/doc_report_dead_links_false/conf.py b/tests/doc_test/doc_report_dead_links_false/conf.py index ae1448dee..891eeeb77 100644 --- a/tests/doc_test/doc_report_dead_links_false/conf.py +++ b/tests/doc_test/doc_report_dead_links_false/conf.py @@ -36,21 +36,19 @@ suppress_warnings = ["needs.link_outgoing"] -needs_extra_links = [ - { - "option": "blocks", +needs_links = { + "blocks": { "incoming": "is blocked by", "outgoing": "blocks", "copy": False, "style": "bold,#AA0000", "allow_dead_links": True, }, - { - "option": "tests", + "tests": { "incoming": "is tested by", "outgoing": "tests", "copy": True, "style": "dashed,#00AA00", "style_part": "dotted,#00AA00", }, -] +} diff --git a/tests/doc_test/doc_report_dead_links_true/conf.py b/tests/doc_test/doc_report_dead_links_true/conf.py index e114d9518..6cbf9d2a7 100644 --- a/tests/doc_test/doc_report_dead_links_true/conf.py +++ b/tests/doc_test/doc_report_dead_links_true/conf.py @@ -34,21 +34,19 @@ }, ] -needs_extra_links = [ - { - "option": "blocks", +needs_links = { + "blocks": { "incoming": "is blocked by", "outgoing": "blocks", "copy": False, "style": "bold,#AA0000", "allow_dead_links": True, }, - { - "option": "tests", + "tests": { "incoming": "is tested by", "outgoing": "tests", "copy": True, "style": "dashed,#00AA00", "style_part": "dotted,#00AA00", }, -] +} diff --git a/tests/doc_test/doc_schema_benchmark/ubproject.toml b/tests/doc_test/doc_schema_benchmark/ubproject.toml index fa31d320d..852beb6c6 100644 --- a/tests/doc_test/doc_schema_benchmark/ubproject.toml +++ b/tests/doc_test/doc_schema_benchmark/ubproject.toml @@ -25,8 +25,7 @@ schema.enum = ["QM", "A", "B", "C", "D"] description = "Approval flag" schema.type = "boolean" -[[needs.extra_links]] -option = "links" +[needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" diff --git a/tests/doc_test/doc_schema_e2e/ubproject.toml b/tests/doc_test/doc_schema_e2e/ubproject.toml index bd53864e4..5657b921a 100644 --- a/tests/doc_test/doc_schema_e2e/ubproject.toml +++ b/tests/doc_test/doc_schema_e2e/ubproject.toml @@ -41,8 +41,7 @@ description = "Review scores" schema.type = "array" schema.items = { "type" = "number" } -[[needs.extra_links]] -option = "links" +[needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" diff --git a/tests/doc_test/doc_schema_example/ubproject.toml b/tests/doc_test/doc_schema_example/ubproject.toml index ae2a8945d..7ac219f1b 100644 --- a/tests/doc_test/doc_schema_example/ubproject.toml +++ b/tests/doc_test/doc_schema_example/ubproject.toml @@ -22,14 +22,12 @@ schema.enum = ["QM", "A", "B", "C", "D"] description = "Approval flag" schema.type = "boolean" -[[needs.extra_links]] -option = "links" +[needs.links.links] outgoing = "links" incoming = "linked by" schema.maxItems = 1 -[[needs.extra_links]] -option = "details" +[needs.links.details] outgoing = "details" incoming = "detailed by by" schema.maxItems = 1 diff --git a/tests/doc_test/filter_doc/conf.py b/tests/doc_test/filter_doc/conf.py index 787367095..471f1daf8 100644 --- a/tests/doc_test/filter_doc/conf.py +++ b/tests/doc_test/filter_doc/conf.py @@ -59,9 +59,8 @@ }, ] -needs_extra_links = [ - { - "option": "triggers", +needs_links = { + "triggers": { "incoming": "triggered by", "outgoing": "triggers", "copy": False, @@ -69,6 +68,6 @@ "style_part": "solid,#777777", "allow_dead_links": True, }, -] +} needs_css = os.path.join(os.path.dirname(__file__), "filter.css") diff --git a/tests/doc_test/variant_doc/conf.py b/tests/doc_test/variant_doc/conf.py index 127ae44a9..8a783e737 100644 --- a/tests/doc_test/variant_doc/conf.py +++ b/tests/doc_test/variant_doc/conf.py @@ -64,12 +64,11 @@ "parse_variants": True, }, } -needs_extra_links = [ - { - "option": "relates", +needs_links = { + "relates": { "parse_variants": True, }, -] +} needs_build_json = True needs_json_remove_defaults = True diff --git a/tests/schema/__snapshots__/test_schema.ambr b/tests/schema/__snapshots__/test_schema.ambr index ea6494502..0cb4a6873 100644 --- a/tests/schema/__snapshots__/test_schema.ambr +++ b/tests/schema/__snapshots__/test_schema.ambr @@ -487,67 +487,67 @@ # name: test_schema_config[both_definitions_and_json_given] "You cannot use both 'needs_schema_definitions' and 'needs_schema_definitions_from_json' at the same time." # --- -# name: test_schema_config[extra_link_array_wrong_item_type] +# name: test_schema_config[extra_option_empty_schema] + "Invalid field 'efforts': Invalid schema: Must have a 'type' field." +# --- +# name: test_schema_config[extra_option_pattern_unsafe_error] ''' - Invalid extra link 'links': Invalid schema: "string" was expected + Invalid field 'efforts': Invalid schema: "^IMPL_(?!SAFE)" is not a "regex" - Failed validating "const" in schema["properties"]["contains"]["$ref"]["properties"]["type"] + Failed validating in schema - On instance["items"]["type"]: - "other" + On instance["properties"]["efforts"]: + "^IMPL_(?!SAFE)" ''' # --- -# name: test_schema_config[extra_link_array_wrong_type] +# name: test_schema_config[extra_option_unknown_keys] ''' - Invalid extra link 'links': Invalid schema: "array" was expected + Invalid field 'efforts': Invalid schema: Additional properties are not allowed ('unknown' was unexpected) - Failed validating "const" in schema["properties"]["type"] + Failed validating "additionalProperties" in schema - On instance["type"]: - "object" + On instance: + {"type":"string","unknown":"unknown"} ''' # --- -# name: test_schema_config[extra_link_pattern_unsafe_error] +# name: test_schema_config[link_array_wrong_item_type] ''' - Invalid extra link 'links': Invalid schema: "^IMPL_(?!SAFE)" is not a "regex" + Invalid link 'links': Invalid schema: "string" was expected - Failed validating in schema + Failed validating "const" in schema["properties"]["contains"]["$ref"]["properties"]["type"] - On instance["properties"]["links"]["items"]: - "^IMPL_(?!SAFE)" + On instance["items"]["type"]: + "other" ''' # --- -# name: test_schema_config[extra_link_unknown_keys] +# name: test_schema_config[link_array_wrong_type] ''' - Invalid extra link 'links': Invalid schema: Additional properties are not allowed ('unknown' was unexpected) + Invalid link 'links': Invalid schema: "array" was expected - Failed validating "additionalProperties" in schema + Failed validating "const" in schema["properties"]["type"] - On instance: - {"items":{"type":"string"},"type":"array","unknown":"unknown"} + On instance["type"]: + "object" ''' # --- -# name: test_schema_config[extra_option_empty_schema] - "Invalid field 'efforts': Invalid schema: Must have a 'type' field." -# --- -# name: test_schema_config[extra_option_pattern_unsafe_error] +# name: test_schema_config[link_pattern_unsafe_error] ''' - Invalid field 'efforts': Invalid schema: "^IMPL_(?!SAFE)" is not a "regex" + Invalid link 'links': Invalid schema: "^IMPL_(?!SAFE)" is not a "regex" Failed validating in schema - On instance["properties"]["efforts"]: + On instance["properties"]["links"]["items"]: "^IMPL_(?!SAFE)" ''' # --- -# name: test_schema_config[extra_option_unknown_keys] +# name: test_schema_config[link_unknown_keys] ''' - Invalid field 'efforts': Invalid schema: Additional properties are not allowed ('unknown' was unexpected) + Invalid link 'links': Invalid schema: Additional properties are not allowed ('unknown' was unexpected) Failed validating "additionalProperties" in schema On instance: - {"type":"string","unknown":"unknown"} + {"items":{"type":"string"},"type":"array","unknown":"unknown"} ''' # --- # name: test_schema_config[local_error] @@ -2947,610 +2947,602 @@ }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-contains] +# name: test_schemas[schema/fixtures/fields-array_unique_items] ''' - ERROR: Need 'SPEC_1' has schema violations: + ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > contains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] + Field: array_field + Need path: IMPL_1 + Schema path: fields > schema > properties > array_field > uniqueItems + Schema message: ["a","a"] has non-unique elements [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-contains].1 +# name: test_schemas[schema/fixtures/fields-array_unique_items].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ - 'SPEC_1': list([ + 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > contains', + 'field': 'array_field', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > array_field > uniqueItems', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'validation_msg': '["a","a"] has non-unique elements', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-contains_error] +# name: test_schemas[schema/fixtures/fields-auto_inject_type] + '' +# --- +# name: test_schemas[schema/fixtures/fields-auto_inject_type].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-auto_inject_type_wrong_const] ''' - ERROR: Need 'SPEC_1' has schema violations: - Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > contains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links + Field: asil Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > contains - Schema message: None of ["SPEC_1"] are valid under the given schema [sn_schema_violation.extra_link_fail] + Schema path: [0] > local > properties > asil > const + Schema message: "QM" was expected [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-contains_error].1 +# name: test_schemas[schema/fixtures/fields-auto_inject_type_wrong_const].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', + 'field': 'asil', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > contains', - 'severity': 'violation', - 'validation_msg': 'None of ["SPEC_1"] are valid under the given schema', - }), - 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - 'SPEC_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > contains', + 'schema_path': '[0] > local > properties > asil > const', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'validation_msg': '"QM" was expected', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-inject_array] +# name: test_schemas[schema/fixtures/fields-coerce_to_boolean] + '' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_boolean].1 + dict({ + 'validated_needs_count': 14, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_boolean_from_string_error] + ''' + /index.rst:1: WARNING: Need could not be created: Field 'approved' is invalid: Cannot convert 'not-a-boolean' to boolean [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_boolean_from_string_error].1 + dict({ + 'validated_needs_count': 0, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer] + '' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_float_error] + ''' + /index.rst:1: WARNING: Need could not be created: Field 'efforts' is invalid: Cannot convert '1.2' to integer [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_float_error].1 + dict({ + 'validated_needs_count': 0, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_string_error] + ''' + /index.rst:1: WARNING: Need could not be created: Field 'efforts' is invalid: Cannot convert 'QM' to integer [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_string_error].1 + dict({ + 'validated_needs_count': 0, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_number] + '' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_number].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_number_from_string_error] + ''' + /index.rst:1: WARNING: Need could not be created: Field 'efforts' is invalid: Cannot convert 'QM' to float [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-coerce_to_number_from_string_error].1 + dict({ + 'validated_needs_count': 0, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-const] + '' +# --- +# name: test_schemas[schema/fixtures/fields-const].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-const_error] ''' - ERROR: Need 'SPEC_1' has schema violations: - Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > minItems - Schema message: [] has less than 2 items [sn_schema_violation.extra_link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links + Field: asil Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > minItems - Schema message: ["SPEC_1"] has less than 2 items [sn_schema_violation.extra_link_fail] + Schema path: [0] > local > properties > asil > const + Schema message: "QM" was expected [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-inject_array].1 +# name: test_schemas[schema/fixtures/fields-const_error].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', + 'field': 'asil', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > minItems', - 'severity': 'violation', - 'validation_msg': '["SPEC_1"] has less than 2 items', - }), - 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - 'SPEC_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > minItems', + 'schema_path': '[0] > local > properties > asil > const', 'severity': 'violation', - 'validation_msg': '[] has less than 2 items', + 'validation_msg': '"QM" was expected', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-items_pattern] +# name: test_schemas[schema/fixtures/fields-enum] '' # --- -# name: test_schemas[schema/fixtures/extra_links-items_pattern].1 +# name: test_schemas[schema/fixtures/fields-enum].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-items_pattern_error] +# name: test_schemas[schema/fixtures/fields-enum_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links.0 + Field: asil Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > items > pattern - Schema message: "SPEC_1" does not match "^[A-Z0-9_]{10,}" [sn_schema_violation.extra_link_fail] + Schema path: [0] > local > properties > asil > enum + Schema message: "E" is not one of "QM", "A" or 3 other candidates [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-items_pattern_error].1 +# name: test_schemas[schema/fixtures/fields-enum_error].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links.0', + 'field': 'asil', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > items > pattern', + 'schema_path': '[0] > local > properties > asil > enum', 'severity': 'violation', - 'validation_msg': '"SPEC_1" does not match "^[A-Z0-9_]{10,}"', + 'validation_msg': '"E" is not one of "QM", "A" or 3 other candidates', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-max_contains] - ''' - ERROR: Need 'SPEC_1' has schema violations: - Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > maxContains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] - ERROR: Need 'SPEC_2' has schema violations: - Severity: violation - Field: links - Need path: SPEC_2 - Schema path: extra_links > schema > properties > links > maxContains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] +# name: test_schemas[schema/fixtures/fields-integer_multiple_of] + '' +# --- +# name: test_schemas[schema/fixtures/fields-integer_multiple_of].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-integer_multiple_of_error] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: efforts + Need path: IMPL_1 + Schema path: fields > schema > properties > efforts > multipleOf + Schema message: 8 is not a multiple of 3 [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-max_contains].1 +# name: test_schemas[schema/fixtures/fields-integer_multiple_of_error].1 dict({ - 'validated_needs_count': 3, + 'validated_needs_count': 1, 'validation_warnings': dict({ - 'SPEC_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > maxContains', - 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', - }), - 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - 'SPEC_2': list([ + 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_2', - 'schema_path': 'extra_links > schema > properties > links > maxContains', + 'field': 'efforts', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > efforts > multipleOf', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'validation_msg': '8 is not a multiple of 3', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-max_contains_error] +# name: test_schemas[schema/fixtures/fields-multiple_errors_per_need] ''' - ERROR: Need 'SPEC_1' has schema violations: - Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > maxContains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] - ERROR: Need 'SPEC_2' has schema violations: + ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links - Need path: SPEC_2 - Schema path: extra_links > schema > properties > links > maxContains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] + Field: priority + Need path: IMPL_1 + Schema path: fields > schema > properties > priority > maximum + Schema message: 8 is greater than the maximum of 5 [sn_schema_violation.field_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links + Field: severity Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > maxContains - Schema message: None of ["SPEC_1","SPEC_2"] are valid under the given schema [sn_schema_violation.extra_link_fail] + Schema path: fields > schema > properties > severity > enum + Schema message: "critical" is not one of "low", "medium" or "high" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-max_contains_error].1 +# name: test_schemas[schema/fixtures/fields-multiple_errors_per_need].1 dict({ - 'validated_needs_count': 3, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', + 'field': 'priority', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > maxContains', - 'severity': 'violation', - 'validation_msg': 'None of ["SPEC_1","SPEC_2"] are valid under the given schema', - }), - 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - 'SPEC_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > maxContains', + 'schema_path': 'fields > schema > properties > priority > maximum', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'validation_msg': '8 is greater than the maximum of 5', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), - ]), - 'SPEC_2': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_2', - 'schema_path': 'extra_links > schema > properties > links > maxContains', + 'field': 'severity', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > severity > enum', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'validation_msg': '"critical" is not one of "low", "medium" or "high"', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-max_items] +# name: test_schemas[schema/fixtures/fields-no_schema] '' # --- -# name: test_schemas[schema/fixtures/extra_links-max_items].1 +# name: test_schemas[schema/fixtures/fields-no_schema].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-number_multiple_of] + '' +# --- +# name: test_schemas[schema/fixtures/fields-number_multiple_of].1 + dict({ + 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-max_items_error] +# name: test_schemas[schema/fixtures/fields-number_multiple_of_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links + Field: efforts Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > maxItems - Schema message: ["SPEC_1","SPEC_2"] has more than 1 item [sn_schema_violation.extra_link_fail] + Schema path: fields > schema > properties > efforts > multipleOf + Schema message: 5.0 is not a multiple of 3.3 [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-max_items_error].1 +# name: test_schemas[schema/fixtures/fields-number_multiple_of_error].1 dict({ - 'validated_needs_count': 3, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', + 'field': 'efforts', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > maxItems', + 'schema_path': 'fields > schema > properties > efforts > multipleOf', 'severity': 'violation', - 'validation_msg': '["SPEC_1","SPEC_2"] has more than 1 item', + 'validation_msg': '5.0 is not a multiple of 3.3', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-min_contains] +# name: test_schemas[schema/fixtures/fields-required] + '' +# --- +# name: test_schemas[schema/fixtures/fields-required].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_below_threshold] + '' +# --- +# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_below_threshold].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_missing] + '' +# --- +# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_missing].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_over_threshold] ''' - ERROR: Need 'SPEC_1' has schema violations: + ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > minContains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] + Need path: IMPL_1 + Schema path: [0] > local > required + User message: Required due to high efforts + Schema message: "asil" is a required property [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-min_contains].1 +# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_over_threshold].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ - 'SPEC_1': list([ + 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > minContains', + 'need_path': 'IMPL_1', + 'schema_path': '[0] > local > required', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'user_msg': 'Required due to high efforts', + 'validation_msg': '"asil" is a required property', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-min_contains_error] +# name: test_schemas[schema/fixtures/fields-required_missing] ''' - ERROR: Need 'SPEC_1' has schema violations: - Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > minContains - Schema message: None of [] are valid under the given schema [sn_schema_violation.extra_link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > minContains - Schema message: None of ["SPEC_1"] are valid under the given schema [sn_schema_violation.extra_link_fail] + Schema path: [0] > local > required + Schema message: "asil" is a required property [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-min_contains_error].1 +# name: test_schemas[schema/fixtures/fields-required_missing].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > minContains', - 'severity': 'violation', - 'validation_msg': 'None of ["SPEC_1"] are valid under the given schema', - }), - 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - 'SPEC_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > minContains', + 'schema_path': '[0] > local > required', 'severity': 'violation', - 'validation_msg': 'None of [] are valid under the given schema', + 'validation_msg': '"asil" is a required property', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-min_items] +# name: test_schemas[schema/fixtures/fields-select_by_docname] ''' - ERROR: Need 'SPEC_1' has schema violations: + ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > minItems - Schema message: [] has less than 1 item [sn_schema_violation.extra_link_fail] + Field: asil + Need path: IMPL_1 + Schema path: [0] > local > properties > asil > const + Schema message: "B" was expected [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-min_items].1 +# name: test_schemas[schema/fixtures/fields-select_by_docname].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ - 'SPEC_1': list([ + 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > minItems', + 'field': 'asil', + 'need_path': 'IMPL_1', + 'schema_path': '[0] > local > properties > asil > const', 'severity': 'violation', - 'validation_msg': '[] has less than 1 item', + 'validation_msg': '"B" was expected', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/extra_links-min_items_error] +# name: test_schemas[schema/fixtures/fields-select_by_docname_no_match] + '' +# --- +# name: test_schemas[schema/fixtures/fields-select_by_docname_no_match].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-select_by_is_external_false] ''' - ERROR: Need 'SPEC_1' has schema violations: - Severity: violation - Field: links - Need path: SPEC_1 - Schema path: extra_links > schema > properties > links > minItems - Schema message: [] has less than 2 items [sn_schema_violation.extra_link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: links + Field: asil Need path: IMPL_1 - Schema path: extra_links > schema > properties > links > minItems - Schema message: ["SPEC_1"] has less than 2 items [sn_schema_violation.extra_link_fail] + Schema path: [0] > local > properties > asil > const + Schema message: "B" was expected [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/extra_links-min_items_error].1 +# name: test_schemas[schema/fixtures/fields-select_by_is_external_false].1 dict({ - 'validated_needs_count': 2, + 'validated_needs_count': 1, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'links', + 'field': 'asil', 'need_path': 'IMPL_1', - 'schema_path': 'extra_links > schema > properties > links > minItems', + 'schema_path': '[0] > local > properties > asil > const', 'severity': 'violation', - 'validation_msg': '["SPEC_1"] has less than 2 items', + 'validation_msg': '"B" was expected', }), 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - 'SPEC_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'links', - 'need_path': 'SPEC_1', - 'schema_path': 'extra_links > schema > properties > links > minItems', - 'severity': 'violation', - 'validation_msg': '[] has less than 2 items', - }), - 'log_lvl': 'error', - 'subtype': 'extra_link_fail', - 'type': 'sn_schema_violation', - }), - ]), - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-array_unique_items] - ''' - ERROR: Need 'IMPL_1' has schema violations: - Severity: violation - Field: array_field - Need path: IMPL_1 - Schema path: fields > schema > properties > array_field > uniqueItems - Schema message: ["a","a"] has non-unique elements [sn_schema_violation.field_fail] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-array_unique_items].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - 'IMPL_1': list([ - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'array_field', - 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > array_field > uniqueItems', - 'severity': 'violation', - 'validation_msg': '["a","a"] has non-unique elements', - }), - 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'local_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-auto_inject_type] +# name: test_schemas[schema/fixtures/fields-select_by_is_external_true_no_match] '' # --- -# name: test_schemas[schema/fixtures/fields-auto_inject_type].1 +# name: test_schemas[schema/fixtures/fields-select_by_is_external_true_no_match].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-auto_inject_type_wrong_const] +# name: test_schemas[schema/fixtures/fields-select_by_is_import_false] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation Field: asil Need path: IMPL_1 Schema path: [0] > local > properties > asil > const - Schema message: "QM" was expected [sn_schema_violation.local_fail] + Schema message: "B" was expected [sn_schema_violation.local_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-auto_inject_type_wrong_const].1 +# name: test_schemas[schema/fixtures/fields-select_by_is_import_false].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3563,7 +3555,7 @@ 'need_path': 'IMPL_1', 'schema_path': '[0] > local > properties > asil > const', 'severity': 'violation', - 'validation_msg': '"QM" was expected', + 'validation_msg': '"B" was expected', }), 'log_lvl': 'error', 'subtype': 'local_fail', @@ -3573,110 +3565,139 @@ }), }) # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_boolean] +# name: test_schemas[schema/fixtures/fields-select_by_is_import_true_no_match] '' # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_boolean].1 - dict({ - 'validated_needs_count': 14, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-coerce_to_boolean_from_string_error] - ''' - /index.rst:1: WARNING: Need could not be created: Field 'approved' is invalid: Cannot convert 'not-a-boolean' to boolean [needs.create_need] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-coerce_to_boolean_from_string_error].1 +# name: test_schemas[schema/fixtures/fields-select_by_is_import_true_no_match].1 dict({ - 'validated_needs_count': 0, + 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_integer] +# name: test_schemas[schema/fixtures/fields-set_type_string] '' # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_integer].1 +# name: test_schemas[schema/fixtures/fields-set_type_string].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_float_error] +# name: test_schemas[schema/fixtures/fields-status_enum] ''' - /index.rst:1: WARNING: Need could not be created: Field 'efforts' is invalid: Cannot convert '1.2' to integer [needs.create_need] + ERROR: Need 'IMPL_2' has schema violations: + Severity: violation + Field: status + Need path: IMPL_2 + Schema path: fields > schema > properties > status > enum + Schema message: "other" is not one of "open", "in progress" or "closed" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_float_error].1 +# name: test_schemas[schema/fixtures/fields-status_enum].1 dict({ - 'validated_needs_count': 0, + 'validated_needs_count': 2, 'validation_warnings': dict({ + 'IMPL_2': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'status', + 'need_path': 'IMPL_2', + 'schema_path': 'fields > schema > properties > status > enum', + 'severity': 'violation', + 'validation_msg': '"other" is not one of "open", "in progress" or "closed"', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_string_error] +# name: test_schemas[schema/fixtures/fields-status_non_nullable] ''' - /index.rst:1: WARNING: Need could not be created: Field 'efforts' is invalid: Cannot convert 'QM' to integer [needs.create_need] + /index.rst:5: WARNING: Need could not be created: Field 'status' is not nullable, but no value or default was given. [needs.create_need] ''' # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_integer_from_string_error].1 +# name: test_schemas[schema/fixtures/fields-status_non_nullable].1 dict({ - 'validated_needs_count': 0, + 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_number] +# name: test_schemas[schema/fixtures/fields-string_format_date] '' # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_number].1 +# name: test_schemas[schema/fixtures/fields-string_format_date].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_number_from_string_error] +# name: test_schemas[schema/fixtures/fields-string_format_date_error] ''' - /index.rst:1: WARNING: Need could not be created: Field 'efforts' is invalid: Cannot convert 'QM' to float [needs.create_need] + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: start_date + Need path: IMPL_1 + Schema path: fields > schema > properties > start_date > format + Schema message: "not-a-date" is not a "date" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-coerce_to_number_from_string_error].1 +# name: test_schemas[schema/fixtures/fields-string_format_date_error].1 dict({ - 'validated_needs_count': 0, + 'validated_needs_count': 1, 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'start_date', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > start_date > format', + 'severity': 'violation', + 'validation_msg': '"not-a-date" is not a "date"', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-const] +# name: test_schemas[schema/fixtures/fields-string_format_date_time] '' # --- -# name: test_schemas[schema/fixtures/fields-const].1 +# name: test_schemas[schema/fixtures/fields-string_format_date_time].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-const_error] +# name: test_schemas[schema/fixtures/fields-string_format_date_time_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: asil + Field: start_date Need path: IMPL_1 - Schema path: [0] > local > properties > asil > const - Schema message: "QM" was expected [sn_schema_violation.local_fail] + Schema path: fields > schema > properties > start_date > format + Schema message: "2025-07-1099:99:99Z" is not a "date-time" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-const_error].1 +# name: test_schemas[schema/fixtures/fields-string_format_date_time_error].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3685,42 +3706,42 @@ 'children': list([ ]), 'details': dict({ - 'field': 'asil', + 'field': 'start_date', 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > properties > asil > const', + 'schema_path': 'fields > schema > properties > start_date > format', 'severity': 'violation', - 'validation_msg': '"QM" was expected', + 'validation_msg': '"2025-07-1099:99:99Z" is not a "date-time"', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-enum] +# name: test_schemas[schema/fixtures/fields-string_format_duration] '' # --- -# name: test_schemas[schema/fixtures/fields-enum].1 +# name: test_schemas[schema/fixtures/fields-string_format_duration].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-enum_error] +# name: test_schemas[schema/fixtures/fields-string_format_duration_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: asil + Field: _duration Need path: IMPL_1 - Schema path: [0] > local > properties > asil > enum - Schema message: "E" is not one of "QM", "A" or 3 other candidates [sn_schema_violation.local_fail] + Schema path: fields > schema > properties > _duration > format + Schema message: "P1Q2Q10DT2H30M" is not a "duration" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-enum_error].1 +# name: test_schemas[schema/fixtures/fields-string_format_duration_error].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3729,42 +3750,42 @@ 'children': list([ ]), 'details': dict({ - 'field': 'asil', + 'field': '_duration', 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > properties > asil > enum', + 'schema_path': 'fields > schema > properties > _duration > format', 'severity': 'violation', - 'validation_msg': '"E" is not one of "QM", "A" or 3 other candidates', + 'validation_msg': '"P1Q2Q10DT2H30M" is not a "duration"', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-integer_multiple_of] +# name: test_schemas[schema/fixtures/fields-string_format_email] '' # --- -# name: test_schemas[schema/fixtures/fields-integer_multiple_of].1 +# name: test_schemas[schema/fixtures/fields-string_format_email].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-integer_multiple_of_error] +# name: test_schemas[schema/fixtures/fields-string_format_email_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: efforts + Field: email Need path: IMPL_1 - Schema path: fields > schema > properties > efforts > multipleOf - Schema message: 8 is not a multiple of 3 [sn_schema_violation.field_fail] + Schema path: fields > schema > properties > email > format + Schema message: "not-a-mail" is not a "email" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-integer_multiple_of_error].1 +# name: test_schemas[schema/fixtures/fields-string_format_email_error].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3773,11 +3794,11 @@ 'children': list([ ]), 'details': dict({ - 'field': 'efforts', + 'field': 'email', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > efforts > multipleOf', + 'schema_path': 'fields > schema > properties > email > format', 'severity': 'violation', - 'validation_msg': '8 is not a multiple of 3', + 'validation_msg': '"not-a-mail" is not a "email"', }), 'log_lvl': 'error', 'subtype': 'field_fail', @@ -3787,24 +3808,28 @@ }), }) # --- -# name: test_schemas[schema/fixtures/fields-multiple_errors_per_need] +# name: test_schemas[schema/fixtures/fields-string_format_time] + '' +# --- +# name: test_schemas[schema/fixtures/fields-string_format_time].1 + dict({ + 'validated_needs_count': 1, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-string_format_time_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: priority - Need path: IMPL_1 - Schema path: fields > schema > properties > priority > maximum - Schema message: 8 is greater than the maximum of 5 [sn_schema_violation.field_fail] - ERROR: Need 'IMPL_1' has schema violations: - Severity: violation - Field: severity + Field: time Need path: IMPL_1 - Schema path: fields > schema > properties > severity > enum - Schema message: "critical" is not one of "low", "medium" or "high" [sn_schema_violation.field_fail] + Schema path: fields > schema > properties > time > format + Schema message: "26:12:13+00:00" is not a "time" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-multiple_errors_per_need].1 +# name: test_schemas[schema/fixtures/fields-string_format_time_error].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3813,25 +3838,11 @@ 'children': list([ ]), 'details': dict({ - 'field': 'priority', + 'field': 'time', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > priority > maximum', + 'schema_path': 'fields > schema > properties > time > format', 'severity': 'violation', - 'validation_msg': '8 is greater than the maximum of 5', - }), - 'log_lvl': 'error', - 'subtype': 'field_fail', - 'type': 'sn_schema_violation', - }), - dict({ - 'children': list([ - ]), - 'details': dict({ - 'field': 'severity', - 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > severity > enum', - 'severity': 'violation', - 'validation_msg': '"critical" is not one of "low", "medium" or "high"', + 'validation_msg': '"26:12:13+00:00" is not a "time"', }), 'log_lvl': 'error', 'subtype': 'field_fail', @@ -3841,38 +3852,28 @@ }), }) # --- -# name: test_schemas[schema/fixtures/fields-no_schema] - '' -# --- -# name: test_schemas[schema/fixtures/fields-no_schema].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-number_multiple_of] +# name: test_schemas[schema/fixtures/fields-string_format_uri] '' # --- -# name: test_schemas[schema/fixtures/fields-number_multiple_of].1 +# name: test_schemas[schema/fixtures/fields-string_format_uri].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-number_multiple_of_error] +# name: test_schemas[schema/fixtures/fields-string_format_uri_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: efforts + Field: uri Need path: IMPL_1 - Schema path: fields > schema > properties > efforts > multipleOf - Schema message: 5.0 is not a multiple of 3.3 [sn_schema_violation.field_fail] + Schema path: fields > schema > properties > uri > format + Schema message: "examplecom" is not a "uri" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-number_multiple_of_error].1 +# name: test_schemas[schema/fixtures/fields-string_format_uri_error].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3881,11 +3882,11 @@ 'children': list([ ]), 'details': dict({ - 'field': 'efforts', + 'field': 'uri', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > efforts > multipleOf', + 'schema_path': 'fields > schema > properties > uri > format', 'severity': 'violation', - 'validation_msg': '5.0 is not a multiple of 3.3', + 'validation_msg': '"examplecom" is not a "uri"', }), 'log_lvl': 'error', 'subtype': 'field_fail', @@ -3895,48 +3896,85 @@ }), }) # --- -# name: test_schemas[schema/fixtures/fields-required] +# name: test_schemas[schema/fixtures/fields-string_format_uuid] '' # --- -# name: test_schemas[schema/fixtures/fields-required].1 +# name: test_schemas[schema/fixtures/fields-string_format_uuid].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_below_threshold] - '' +# name: test_schemas[schema/fixtures/fields-string_format_uuid_error] + ''' + ERROR: Need 'IMPL_1' has schema violations: + Severity: violation + Field: uuid + Need path: IMPL_1 + Schema path: fields > schema > properties > uuid > format + Schema message: "deadbeef-deadbeef-deadbeef-deadbeef" is not a "uuid" [sn_schema_violation.field_fail] + + ''' # --- -# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_below_threshold].1 +# name: test_schemas[schema/fixtures/fields-string_format_uuid_error].1 dict({ 'validated_needs_count': 1, + 'validation_warnings': dict({ + 'IMPL_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'uuid', + 'need_path': 'IMPL_1', + 'schema_path': 'fields > schema > properties > uuid > format', + 'severity': 'violation', + 'validation_msg': '"deadbeef-deadbeef-deadbeef-deadbeef" is not a "uuid"', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + }), + }) +# --- +# name: test_schemas[schema/fixtures/fields-string_non_nullable] + ''' + /index.rst:1: WARNING: Need could not be created: Field 'asil' is not nullable, but no value or default was given. [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-string_non_nullable].1 + dict({ + 'validated_needs_count': 0, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_missing] +# name: test_schemas[schema/fixtures/fields-string_type] '' # --- -# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_missing].1 +# name: test_schemas[schema/fixtures/fields-string_type].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_over_threshold] +# name: test_schemas[schema/fixtures/fields-tags_enum] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation + Field: tags.1 Need path: IMPL_1 - Schema path: [0] > local > required - User message: Required due to high efforts - Schema message: "asil" is a required property [sn_schema_violation.local_fail] + Schema path: fields > schema > properties > tags > items > enum + Schema message: "b" is not one of "a" or "c" [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-required_based_on_select_field_over_threshold].1 +# name: test_schemas[schema/fixtures/fields-tags_enum].1 dict({ 'validated_needs_count': 1, 'validation_warnings': dict({ @@ -3945,675 +3983,637 @@ 'children': list([ ]), 'details': dict({ + 'field': 'tags.1', 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > required', + 'schema_path': 'fields > schema > properties > tags > items > enum', 'severity': 'violation', - 'user_msg': 'Required due to high efforts', - 'validation_msg': '"asil" is a required property', + 'validation_msg': '"b" is not one of "a" or "c"', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-required_missing] +# name: test_schemas[schema/fixtures/fields-title_length] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation + Field: title Need path: IMPL_1 - Schema path: [0] > local > required - Schema message: "asil" is a required property [sn_schema_violation.local_fail] + Schema path: fields > schema > properties > title > minLength + Schema message: "too short title" is shorter than 16 characters [sn_schema_violation.field_fail] + ERROR: Need 'IMPL_3' has schema violations: + Severity: violation + Field: title + Need path: IMPL_3 + Schema path: fields > schema > properties > title > maxLength + Schema message: "this title is too long" is longer than 20 characters [sn_schema_violation.field_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-required_missing].1 +# name: test_schemas[schema/fixtures/fields-title_length].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 3, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ + 'field': 'title', 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > required', + 'schema_path': 'fields > schema > properties > title > minLength', 'severity': 'violation', - 'validation_msg': '"asil" is a required property', + 'validation_msg': '"too short title" is shorter than 16 characters', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'field_fail', + 'type': 'sn_schema_violation', + }), + ]), + 'IMPL_3': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'title', + 'need_path': 'IMPL_3', + 'schema_path': 'fields > schema > properties > title > maxLength', + 'severity': 'violation', + 'validation_msg': '"this title is too long" is longer than 20 characters', + }), + 'log_lvl': 'error', + 'subtype': 'field_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-select_by_docname] +# name: test_schemas[schema/fixtures/fields-wrong_type] ''' - ERROR: Need 'IMPL_1' has schema violations: + /index.rst:1: WARNING: Need could not be created: Field 'asil' is invalid: Cannot convert 'QM' to integer [needs.create_need] + + ''' +# --- +# name: test_schemas[schema/fixtures/fields-wrong_type].1 + dict({ + 'validated_needs_count': 0, + 'validation_warnings': dict({ + }), + }) +# --- +# name: test_schemas[schema/fixtures/links-contains] + ''' + ERROR: Need 'SPEC_1' has schema violations: Severity: violation - Field: asil - Need path: IMPL_1 - Schema path: [0] > local > properties > asil > const - Schema message: "B" was expected [sn_schema_violation.local_fail] + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > contains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-select_by_docname].1 +# name: test_schemas[schema/fixtures/links-contains].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ - 'IMPL_1': list([ + 'SPEC_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'asil', - 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > properties > asil > const', + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > contains', 'severity': 'violation', - 'validation_msg': '"B" was expected', + 'validation_msg': 'None of [] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-select_by_docname_no_match] - '' -# --- -# name: test_schemas[schema/fixtures/fields-select_by_docname_no_match].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-select_by_is_external_false] +# name: test_schemas[schema/fixtures/links-contains_error] ''' + ERROR: Need 'SPEC_1' has schema violations: + Severity: violation + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > contains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: asil + Field: links Need path: IMPL_1 - Schema path: [0] > local > properties > asil > const - Schema message: "B" was expected [sn_schema_violation.local_fail] + Schema path: links > schema > properties > links > contains + Schema message: None of ["SPEC_1"] are valid under the given schema [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-select_by_is_external_false].1 +# name: test_schemas[schema/fixtures/links-contains_error].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'asil', + 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > properties > asil > const', + 'schema_path': 'links > schema > properties > links > contains', 'severity': 'violation', - 'validation_msg': '"B" was expected', + 'validation_msg': 'None of ["SPEC_1"] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'link_fail', + 'type': 'sn_schema_violation', + }), + ]), + 'SPEC_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > contains', + 'severity': 'violation', + 'validation_msg': 'None of [] are valid under the given schema', + }), + 'log_lvl': 'error', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-select_by_is_external_true_no_match] - '' -# --- -# name: test_schemas[schema/fixtures/fields-select_by_is_external_true_no_match].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-select_by_is_import_false] +# name: test_schemas[schema/fixtures/links-inject_array] ''' + ERROR: Need 'SPEC_1' has schema violations: + Severity: violation + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > minItems + Schema message: [] has less than 2 items [sn_schema_violation.link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: asil + Field: links Need path: IMPL_1 - Schema path: [0] > local > properties > asil > const - Schema message: "B" was expected [sn_schema_violation.local_fail] + Schema path: links > schema > properties > links > minItems + Schema message: ["SPEC_1"] has less than 2 items [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-select_by_is_import_false].1 +# name: test_schemas[schema/fixtures/links-inject_array].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'asil', + 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': '[0] > local > properties > asil > const', + 'schema_path': 'links > schema > properties > links > minItems', 'severity': 'violation', - 'validation_msg': '"B" was expected', + 'validation_msg': '["SPEC_1"] has less than 2 items', }), 'log_lvl': 'error', - 'subtype': 'local_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-select_by_is_import_true_no_match] - '' -# --- -# name: test_schemas[schema/fixtures/fields-select_by_is_import_true_no_match].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-set_type_string] - '' -# --- -# name: test_schemas[schema/fixtures/fields-set_type_string].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-status_enum] - ''' - ERROR: Need 'IMPL_2' has schema violations: - Severity: violation - Field: status - Need path: IMPL_2 - Schema path: fields > schema > properties > status > enum - Schema message: "other" is not one of "open", "in progress" or "closed" [sn_schema_violation.field_fail] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-status_enum].1 - dict({ - 'validated_needs_count': 2, - 'validation_warnings': dict({ - 'IMPL_2': list([ + 'SPEC_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'status', - 'need_path': 'IMPL_2', - 'schema_path': 'fields > schema > properties > status > enum', + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > minItems', 'severity': 'violation', - 'validation_msg': '"other" is not one of "open", "in progress" or "closed"', + 'validation_msg': '[] has less than 2 items', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-status_non_nullable] - ''' - /index.rst:5: WARNING: Need could not be created: Field 'status' is not nullable, but no value or default was given. [needs.create_need] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-status_non_nullable].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_date] +# name: test_schemas[schema/fixtures/links-items_pattern] '' # --- -# name: test_schemas[schema/fixtures/fields-string_format_date].1 +# name: test_schemas[schema/fixtures/links-items_pattern].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_date_error] +# name: test_schemas[schema/fixtures/links-items_pattern_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: start_date + Field: links.0 Need path: IMPL_1 - Schema path: fields > schema > properties > start_date > format - Schema message: "not-a-date" is not a "date" [sn_schema_violation.field_fail] + Schema path: links > schema > properties > links > items > pattern + Schema message: "SPEC_1" does not match "^[A-Z0-9_]{10,}" [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-string_format_date_error].1 +# name: test_schemas[schema/fixtures/links-items_pattern_error].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'start_date', + 'field': 'links.0', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > start_date > format', + 'schema_path': 'links > schema > properties > links > items > pattern', 'severity': 'violation', - 'validation_msg': '"not-a-date" is not a "date"', + 'validation_msg': '"SPEC_1" does not match "^[A-Z0-9_]{10,}"', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_date_time] - '' -# --- -# name: test_schemas[schema/fixtures/fields-string_format_date_time].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_date_time_error] +# name: test_schemas[schema/fixtures/links-max_contains] ''' - ERROR: Need 'IMPL_1' has schema violations: + ERROR: Need 'SPEC_1' has schema violations: Severity: violation - Field: start_date - Need path: IMPL_1 - Schema path: fields > schema > properties > start_date > format - Schema message: "2025-07-1099:99:99Z" is not a "date-time" [sn_schema_violation.field_fail] + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > maxContains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] + ERROR: Need 'SPEC_2' has schema violations: + Severity: violation + Field: links + Need path: SPEC_2 + Schema path: links > schema > properties > links > maxContains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-string_format_date_time_error].1 +# name: test_schemas[schema/fixtures/links-max_contains].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 3, 'validation_warnings': dict({ - 'IMPL_1': list([ + 'SPEC_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'start_date', - 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > start_date > format', + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > maxContains', 'severity': 'violation', - 'validation_msg': '"2025-07-1099:99:99Z" is not a "date-time"', + 'validation_msg': 'None of [] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_duration] - '' -# --- -# name: test_schemas[schema/fixtures/fields-string_format_duration].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_duration_error] - ''' - ERROR: Need 'IMPL_1' has schema violations: - Severity: violation - Field: _duration - Need path: IMPL_1 - Schema path: fields > schema > properties > _duration > format - Schema message: "P1Q2Q10DT2H30M" is not a "duration" [sn_schema_violation.field_fail] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-string_format_duration_error].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - 'IMPL_1': list([ + 'SPEC_2': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': '_duration', - 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > _duration > format', + 'field': 'links', + 'need_path': 'SPEC_2', + 'schema_path': 'links > schema > properties > links > maxContains', 'severity': 'violation', - 'validation_msg': '"P1Q2Q10DT2H30M" is not a "duration"', + 'validation_msg': 'None of [] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_email] - '' -# --- -# name: test_schemas[schema/fixtures/fields-string_format_email].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_email_error] +# name: test_schemas[schema/fixtures/links-max_contains_error] ''' + ERROR: Need 'SPEC_1' has schema violations: + Severity: violation + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > maxContains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] + ERROR: Need 'SPEC_2' has schema violations: + Severity: violation + Field: links + Need path: SPEC_2 + Schema path: links > schema > properties > links > maxContains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: email + Field: links Need path: IMPL_1 - Schema path: fields > schema > properties > email > format - Schema message: "not-a-mail" is not a "email" [sn_schema_violation.field_fail] + Schema path: links > schema > properties > links > maxContains + Schema message: None of ["SPEC_1","SPEC_2"] are valid under the given schema [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-string_format_email_error].1 +# name: test_schemas[schema/fixtures/links-max_contains_error].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 3, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'email', + 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > email > format', + 'schema_path': 'links > schema > properties > links > maxContains', 'severity': 'violation', - 'validation_msg': '"not-a-mail" is not a "email"', + 'validation_msg': 'None of ["SPEC_1","SPEC_2"] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', + 'type': 'sn_schema_violation', + }), + ]), + 'SPEC_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > maxContains', + 'severity': 'violation', + 'validation_msg': 'None of [] are valid under the given schema', + }), + 'log_lvl': 'error', + 'subtype': 'link_fail', + 'type': 'sn_schema_violation', + }), + ]), + 'SPEC_2': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'links', + 'need_path': 'SPEC_2', + 'schema_path': 'links > schema > properties > links > maxContains', + 'severity': 'violation', + 'validation_msg': 'None of [] are valid under the given schema', + }), + 'log_lvl': 'error', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_time] +# name: test_schemas[schema/fixtures/links-max_items] '' # --- -# name: test_schemas[schema/fixtures/fields-string_format_time].1 +# name: test_schemas[schema/fixtures/links-max_items].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_time_error] +# name: test_schemas[schema/fixtures/links-max_items_error] ''' ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: time + Field: links Need path: IMPL_1 - Schema path: fields > schema > properties > time > format - Schema message: "26:12:13+00:00" is not a "time" [sn_schema_violation.field_fail] + Schema path: links > schema > properties > links > maxItems + Schema message: ["SPEC_1","SPEC_2"] has more than 1 item [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-string_format_time_error].1 +# name: test_schemas[schema/fixtures/links-max_items_error].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 3, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'time', + 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > time > format', + 'schema_path': 'links > schema > properties > links > maxItems', 'severity': 'violation', - 'validation_msg': '"26:12:13+00:00" is not a "time"', + 'validation_msg': '["SPEC_1","SPEC_2"] has more than 1 item', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_uri] - '' -# --- -# name: test_schemas[schema/fixtures/fields-string_format_uri].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_uri_error] +# name: test_schemas[schema/fixtures/links-min_contains] ''' - ERROR: Need 'IMPL_1' has schema violations: + ERROR: Need 'SPEC_1' has schema violations: Severity: violation - Field: uri - Need path: IMPL_1 - Schema path: fields > schema > properties > uri > format - Schema message: "examplecom" is not a "uri" [sn_schema_violation.field_fail] + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > minContains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-string_format_uri_error].1 +# name: test_schemas[schema/fixtures/links-min_contains].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ - 'IMPL_1': list([ + 'SPEC_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'uri', - 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > uri > format', + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > minContains', 'severity': 'violation', - 'validation_msg': '"examplecom" is not a "uri"', + 'validation_msg': 'None of [] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_format_uuid] - '' -# --- -# name: test_schemas[schema/fixtures/fields-string_format_uuid].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_format_uuid_error] +# name: test_schemas[schema/fixtures/links-min_contains_error] ''' + ERROR: Need 'SPEC_1' has schema violations: + Severity: violation + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > minContains + Schema message: None of [] are valid under the given schema [sn_schema_violation.link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: uuid + Field: links Need path: IMPL_1 - Schema path: fields > schema > properties > uuid > format - Schema message: "deadbeef-deadbeef-deadbeef-deadbeef" is not a "uuid" [sn_schema_violation.field_fail] + Schema path: links > schema > properties > links > minContains + Schema message: None of ["SPEC_1"] are valid under the given schema [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-string_format_uuid_error].1 +# name: test_schemas[schema/fixtures/links-min_contains_error].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'uuid', + 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > uuid > format', + 'schema_path': 'links > schema > properties > links > minContains', 'severity': 'violation', - 'validation_msg': '"deadbeef-deadbeef-deadbeef-deadbeef" is not a "uuid"', + 'validation_msg': 'None of ["SPEC_1"] are valid under the given schema', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', + 'type': 'sn_schema_violation', + }), + ]), + 'SPEC_1': list([ + dict({ + 'children': list([ + ]), + 'details': dict({ + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > minContains', + 'severity': 'violation', + 'validation_msg': 'None of [] are valid under the given schema', + }), + 'log_lvl': 'error', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-string_non_nullable] - ''' - /index.rst:1: WARNING: Need could not be created: Field 'asil' is not nullable, but no value or default was given. [needs.create_need] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-string_non_nullable].1 - dict({ - 'validated_needs_count': 0, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-string_type] - '' -# --- -# name: test_schemas[schema/fixtures/fields-string_type].1 - dict({ - 'validated_needs_count': 1, - 'validation_warnings': dict({ - }), - }) -# --- -# name: test_schemas[schema/fixtures/fields-tags_enum] +# name: test_schemas[schema/fixtures/links-min_items] ''' - ERROR: Need 'IMPL_1' has schema violations: + ERROR: Need 'SPEC_1' has schema violations: Severity: violation - Field: tags.1 - Need path: IMPL_1 - Schema path: fields > schema > properties > tags > items > enum - Schema message: "b" is not one of "a" or "c" [sn_schema_violation.field_fail] + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > minItems + Schema message: [] has less than 1 item [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-tags_enum].1 +# name: test_schemas[schema/fixtures/links-min_items].1 dict({ - 'validated_needs_count': 1, + 'validated_needs_count': 2, 'validation_warnings': dict({ - 'IMPL_1': list([ + 'SPEC_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'tags.1', - 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > tags > items > enum', + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > minItems', 'severity': 'violation', - 'validation_msg': '"b" is not one of "a" or "c"', + 'validation_msg': '[] has less than 1 item', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-title_length] +# name: test_schemas[schema/fixtures/links-min_items_error] ''' + ERROR: Need 'SPEC_1' has schema violations: + Severity: violation + Field: links + Need path: SPEC_1 + Schema path: links > schema > properties > links > minItems + Schema message: [] has less than 2 items [sn_schema_violation.link_fail] ERROR: Need 'IMPL_1' has schema violations: Severity: violation - Field: title + Field: links Need path: IMPL_1 - Schema path: fields > schema > properties > title > minLength - Schema message: "too short title" is shorter than 16 characters [sn_schema_violation.field_fail] - ERROR: Need 'IMPL_3' has schema violations: - Severity: violation - Field: title - Need path: IMPL_3 - Schema path: fields > schema > properties > title > maxLength - Schema message: "this title is too long" is longer than 20 characters [sn_schema_violation.field_fail] + Schema path: links > schema > properties > links > minItems + Schema message: ["SPEC_1"] has less than 2 items [sn_schema_violation.link_fail] ''' # --- -# name: test_schemas[schema/fixtures/fields-title_length].1 +# name: test_schemas[schema/fixtures/links-min_items_error].1 dict({ - 'validated_needs_count': 3, + 'validated_needs_count': 2, 'validation_warnings': dict({ 'IMPL_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'title', + 'field': 'links', 'need_path': 'IMPL_1', - 'schema_path': 'fields > schema > properties > title > minLength', + 'schema_path': 'links > schema > properties > links > minItems', 'severity': 'violation', - 'validation_msg': '"too short title" is shorter than 16 characters', + 'validation_msg': '["SPEC_1"] has less than 2 items', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), - 'IMPL_3': list([ + 'SPEC_1': list([ dict({ 'children': list([ ]), 'details': dict({ - 'field': 'title', - 'need_path': 'IMPL_3', - 'schema_path': 'fields > schema > properties > title > maxLength', + 'field': 'links', + 'need_path': 'SPEC_1', + 'schema_path': 'links > schema > properties > links > minItems', 'severity': 'violation', - 'validation_msg': '"this title is too long" is longer than 20 characters', + 'validation_msg': '[] has less than 2 items', }), 'log_lvl': 'error', - 'subtype': 'field_fail', + 'subtype': 'link_fail', 'type': 'sn_schema_violation', }), ]), }), }) # --- -# name: test_schemas[schema/fixtures/fields-wrong_type] - ''' - /index.rst:1: WARNING: Need could not be created: Field 'asil' is invalid: Cannot convert 'QM' to integer [needs.create_need] - - ''' -# --- -# name: test_schemas[schema/fixtures/fields-wrong_type].1 - dict({ - 'validated_needs_count': 0, - 'validation_warnings': dict({ - }), - }) -# --- # name: test_schemas[schema/fixtures/network-link_chain_hop_1_min_contains_error] ''' ERROR: Need 'IMPL_SAFE' has schema violations: diff --git a/tests/schema/fixtures/config.yml b/tests/schema/fixtures/config.yml index 9b7eee869..08eef9b35 100644 --- a/tests/schema/fixtures/config.yml +++ b/tests/schema/fixtures/config.yml @@ -36,25 +36,23 @@ tags_wrong_items_type: schema.items.type = "boolean" rst: "" -extra_link_array_wrong_type: +link_array_wrong_type: conf: | extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "object" rst: "" -extra_link_array_wrong_item_type: +link_array_wrong_item_type: conf: | extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" @@ -333,13 +331,12 @@ extra_option_unknown_keys: schema.unknown = "unknown" rst: "" -extra_link_unknown_keys: +link_unknown_keys: conf: | extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.unknown = "unknown" @@ -355,13 +352,12 @@ extra_option_pattern_unsafe_error: schema.pattern = "^IMPL_(?!SAFE)" rst: "" -extra_link_pattern_unsafe_error: +link_pattern_unsafe_error: conf: | extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.items = { pattern = "^IMPL_(?!SAFE)" } diff --git a/tests/schema/fixtures/fields.yml b/tests/schema/fixtures/fields.yml index 347c499c0..912bb1126 100644 --- a/tests/schema/fixtures/fields.yml +++ b/tests/schema/fixtures/fields.yml @@ -920,6 +920,8 @@ tags_enum: needs_from_toml = "ubproject.toml" ubproject: | [needs.fields.tags] + schema.type = "array" + schema.items.type = "string" schema.items.enum = ["a", "c"] rst: | .. impl:: title diff --git a/tests/schema/fixtures/extra_links.yml b/tests/schema/fixtures/links.yml similarity index 88% rename from tests/schema/fixtures/extra_links.yml rename to tests/schema/fixtures/links.yml index e242674a4..57ca00b41 100644 --- a/tests/schema/fixtures/extra_links.yml +++ b/tests/schema/fixtures/links.yml @@ -3,8 +3,7 @@ inject_array: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.minItems = 2 @@ -21,8 +20,7 @@ items_pattern: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.items = { type = "string", pattern = "^[A-Z0-9_]{3,}" } @@ -39,8 +37,7 @@ items_pattern_error: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.items = { type = "string", pattern = "^[A-Z0-9_]{10,}" } @@ -57,8 +54,7 @@ min_items: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" @@ -76,8 +72,7 @@ min_items_error: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" @@ -95,8 +90,7 @@ max_items: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" @@ -114,8 +108,7 @@ max_items_error: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.maxItems = 1 @@ -135,8 +128,7 @@ contains: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.contains = { pattern = "^SPEC_" } @@ -153,8 +145,7 @@ contains_error: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.contains = { pattern = "^REQ_" } @@ -171,8 +162,7 @@ min_contains: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.contains = { pattern = "^SPEC_" } @@ -190,8 +180,7 @@ min_contains_error: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" @@ -210,8 +199,7 @@ max_contains: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.contains = { pattern = "^SPEC_" } @@ -232,8 +220,7 @@ max_contains_error: extensions = ["sphinx_needs"] needs_from_toml = "ubproject.toml" ubproject: | - [[needs.extra_links]] - option = "links" + [needs.links.links] outgoing = "links" incoming = "linked by" schema.type = "array" diff --git a/tests/schema/fixtures/network.yml b/tests/schema/fixtures/network.yml index 2eb166790..9042dfb55 100644 --- a/tests/schema/fixtures/network.yml +++ b/tests/schema/fixtures/network.yml @@ -633,12 +633,10 @@ link_name_contains: needs_from_toml = "ubproject.toml" needs_schema_definitions_from_json = "schemas.json" ubproject: | - [[needs.extra_links]] - option = "contains" + [needs.links.contains] outgoing = "contains" incoming = "contained by" - [[needs.extra_links]] - option = "items" + [needs.links.items] outgoing = "items" incoming = "item of" rst: | diff --git a/tests/schema/test_generate_schema.py b/tests/schema/test_generate_schema.py index 9ef7f564a..4a0d477eb 100644 --- a/tests/schema/test_generate_schema.py +++ b/tests/schema/test_generate_schema.py @@ -18,13 +18,13 @@ from syrupy.extensions.json import JSONSnapshotExtension from sphinx_needs.schema.config import ( - ExtraLinkItemSchemaType, - ExtraLinkSchemaType, FieldBooleanSchemaType, FieldIntegerSchemaType, FieldMultiValueSchemaType, FieldNumberSchemaType, FieldStringSchemaType, + LinkItemSchemaType, + LinkSchemaType, SchemasRootType, ) @@ -110,8 +110,8 @@ def snapshot_json(snapshot): @pytest.mark.parametrize( "klass", [ - ExtraLinkItemSchemaType, - ExtraLinkSchemaType, + LinkItemSchemaType, + LinkSchemaType, FieldBooleanSchemaType, FieldIntegerSchemaType, FieldMultiValueSchemaType, diff --git a/tests/schema/test_schema.py b/tests/schema/test_schema.py index a448ff8a7..2a6ba705c 100644 --- a/tests/schema/test_schema.py +++ b/tests/schema/test_schema.py @@ -41,7 +41,7 @@ def test_schema_config( @pytest.mark.fixture_file( - "schema/fixtures/extra_links.yml", + "schema/fixtures/links.yml", "schema/fixtures/fields.yml", "schema/fixtures/network.yml", "schema/fixtures/reporting.yml", diff --git a/tests/test_extra_links.py b/tests/test_extra_links.py deleted file mode 100644 index e089d38f0..000000000 --- a/tests/test_extra_links.py +++ /dev/null @@ -1,42 +0,0 @@ -from pathlib import Path - -import pytest - - -@pytest.mark.parametrize( - "test_app", - [{"buildername": "html", "srcdir": "doc_test/doc_extra_links"}], - indirect=True, -) -def test_extra_links_html(test_app): - app = test_app - app.build() - html = Path(app.outdir, "index.html").read_text() - assert "TEST_001" in html - assert "tested by" in html - assert "tests" in html - assert "blocked by" in html - assert "blocks" in html - - # Check for correct dead_links handling - assert 'DEAD_LINK_ALLOWED' in html - assert ( - 'DEAD_LINK_NOT_ALLOWED' in html - ) - assert 'REQ_005.invalid' in html - - -@pytest.mark.parametrize( - "test_app", - [{"buildername": "latex", "srcdir": "doc_test/doc_extra_links"}], - indirect=True, -) -def test_extra_links_latex(test_app): - app = test_app - app.build() - tex = Path(app.outdir, "needstestdocs.tex").read_text() - assert "TEST_001" in tex - assert "tested by" in tex - assert "tests" in tex - assert "blocked by" in tex - assert "blocks" in tex diff --git a/tests/test_field_defaults.py b/tests/test_field_defaults.py index 07580b8ea..87f2135e7 100644 --- a/tests/test_field_defaults.py +++ b/tests/test_field_defaults.py @@ -40,7 +40,7 @@ def test_doc_global_option(test_app, snapshot): test_app._warning.getvalue().replace(str(test_app.srcdir) + os.sep, "srcdir/") ).splitlines() assert warnings == [ - 'WARNING: Config option "needs_global_options" is deprecated. Please use needs_fields and needs_extra_links instead. [needs.deprecated]', + 'WARNING: Config option "needs_global_options" is deprecated. Please use needs_fields and needs_links instead. [needs.deprecated]', "WARNING: needs_global_options['link3']['default'] value is incorrect: Invalid value for field 'link3': 1 [needs.config]", "WARNING: needs_global_options['bad_value_type']['default'] value is incorrect: Invalid value for field 'bad_value_type': 1.27 [needs.config]", "WARNING: needs_global_options['too_many_params']['predicates'] value is incorrect: defaults must be a list of (filter, value) pairs. [needs.config]", @@ -79,7 +79,7 @@ def test_doc_field_defaults(test_app, snapshot): assert warnings == [ "WARNING: needs_fields['bad_value_type']['default'] value is incorrect: Invalid value for field 'bad_value_type': 1.27 [needs.config]", "WARNING: needs_fields['too_many_params']['predicates'] value is incorrect: defaults must be a list of (filter, value) pairs. [needs.config]", - "WARNING: needs_extra_links['link3']['default'] value is incorrect: Invalid value for field 'link3': 1 [needs.config]", + "WARNING: needs_links['link3']['default'] value is incorrect: Invalid value for field 'link3': 1 [needs.config]", ] needs_schema = SphinxNeedsData(test_app.env).get_schema() diff --git a/tests/test_links.py b/tests/test_links.py new file mode 100644 index 000000000..85cbfcc65 --- /dev/null +++ b/tests/test_links.py @@ -0,0 +1,58 @@ +import os +from pathlib import Path + +import pytest +from sphinx.util.console import strip_colors + + +@pytest.mark.parametrize( + "test_app", + [{"buildername": "html", "srcdir": "doc_test/doc_links", "no_plantuml": False}], + indirect=True, +) +def test_links_html(test_app): + app = test_app + app.build() + + warnings = strip_colors( + app._warning.getvalue().replace(str(app.srcdir) + os.sep, "srcdir/") + ).splitlines() + # print(warnings) + assert warnings == [ + 'WARNING: Config option "needs_extra_links" is deprecated. Please use "needs_links" instead. [needs.deprecated]', + "srcdir/index.rst:7: WARNING: Need 'REQ_001' has unknown outgoing link 'DEAD_LINK_NOT_ALLOWED' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:7: WARNING: Need 'REQ_001' has unknown outgoing link 'DEAD_LINK_ALLOWED' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:7: WARNING: Need 'REQ_001' has unknown outgoing link 'DEAD_LINK_ALLOWED' in field 'blocks' [needs.link_outgoing]", + "srcdir/index.rst:12: WARNING: Need 'REQ_002' has unknown outgoing link 'ARGH_123' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:49: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + ] + + html = Path(app.outdir, "index.html").read_text() + assert "TEST_001" in html + assert "tested by" in html + assert "tests" in html + assert "blocked by" in html + assert "blocks" in html + + # Check for correct dead_links handling + assert 'DEAD_LINK_ALLOWED' in html + assert ( + 'DEAD_LINK_NOT_ALLOWED' in html + ) + assert 'REQ_005.invalid' in html + + +@pytest.mark.parametrize( + "test_app", + [{"buildername": "latex", "srcdir": "doc_test/doc_links", "no_plantuml": False}], + indirect=True, +) +def test_links_latex(test_app): + app = test_app + app.build() + tex = Path(app.outdir, "needstestdocs.tex").read_text() + assert "TEST_001" in tex + assert "tested by" in tex + assert "tests" in tex + assert "blocked by" in tex + assert "blocks" in tex diff --git a/tests/test_needreport.py b/tests/test_needreport.py index 0e0208e22..825605876 100644 --- a/tests/test_needreport.py +++ b/tests/test_needreport.py @@ -26,6 +26,6 @@ def test_doc_needarch(test_app): html = Path(app.outdir, "index.html").read_text(encoding="utf8") assert "Need Types" in html - assert "Need Extra Links" in html - assert "Need Extra Options" in html + assert "Need Links" in html + assert "Need Fields" in html assert "Need Metrics" in html diff --git a/tests/test_report_dead_links.py b/tests/test_report_dead_links.py index 7fa657c0f..b984a0c79 100644 --- a/tests/test_report_dead_links.py +++ b/tests/test_report_dead_links.py @@ -1,3 +1,4 @@ +import os import subprocess from pathlib import Path @@ -20,11 +21,13 @@ def test_needs_dead_links_warnings(test_app): ) # check there are expected warnings - stderr = strip_colors(output.stderr.decode("utf-8")) + stderr = strip_colors( + output.stderr.decode("utf-8").replace(str(app.srcdir) + os.sep, "srcdir/") + ) expected_warnings = [ - f"{Path(str(app.srcdir)) / 'index.rst'}:17: WARNING: Need 'REQ_004' has unknown outgoing link 'ANOTHER_DEAD_LINK' in field 'links' [needs.link_outgoing]", - f"{Path(str(app.srcdir)) / 'index.rst'}:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'links' [needs.link_outgoing]", - f"{Path(str(app.srcdir)) / 'index.rst'}:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + "srcdir/index.rst:17: WARNING: Need 'REQ_004' has unknown outgoing link 'ANOTHER_DEAD_LINK' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'links' [needs.link_outgoing]", ] assert stderr.splitlines() == expected_warnings @@ -45,11 +48,13 @@ def test_needs_dead_links_warnings_needs_builder(test_app): ) # check there are expected warnings - stderr = strip_colors(output.stderr.decode("utf-8")) + stderr = strip_colors( + output.stderr.decode("utf-8").replace(str(app.srcdir) + os.sep, "srcdir/") + ) expected_warnings = [ - f"{Path(str(app.srcdir)) / 'index.rst'}:17: WARNING: Need 'REQ_004' has unknown outgoing link 'ANOTHER_DEAD_LINK' in field 'links' [needs.link_outgoing]", - f"{Path(str(app.srcdir)) / 'index.rst'}:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'links' [needs.link_outgoing]", - f"{Path(str(app.srcdir)) / 'index.rst'}:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + "srcdir/index.rst:17: WARNING: Need 'REQ_004' has unknown outgoing link 'ANOTHER_DEAD_LINK' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'links' [needs.link_outgoing]", ] assert stderr.splitlines() == expected_warnings