Repository: datacontract/datacontract-cli

Issue:
Definitions referenced by nested fields are not validated correctly
I have defined a simple data contract that uses definitions on top level and the same definitions in an nested field.
```yaml
dataContractSpecification: 1.1.0
id: urn:datacontract:mycontract
info:
  title: My simple contract
  version: 1.0.0
  description: |
    Test contract for testing definitions
  owner: Test
  contact:
    name: Test user
servers:
  integration:
    type: local
    path: ./simple.json
    format: json
models:
  Result:
    type: object
    description: The result of an aggregation
    fields:
      id:
        $ref: '#/definitions/MyId'
        required: true
        description: The stable ID of the result
      status:
        $ref: '#/definitions/MyStatus'
        required: true
        description: The status of the result
      myData:
        type: array
        required: true
        description: Payload containing the information
        items:
          name: MyData
          description: Payload containing the information
          type: object
          fields:
            dataId:
              $ref: '#/definitions/MyId'
              required: true
              description: The ID of the data
            dataStatus:
              $ref: '#/definitions/MyStatus'
              required: true
              description: The status of the data
definitions:
  MyStatus:
    title: My status
    type: string
    enum:
      - active
      - created
      - finished
      - deleted
  MyId:
    title: My ID
    type: text
    format: uuid
    examples:
      - 243c25e5-a081-43a9-aeab-6d5d5b6cb5e2
```

I created the data file `simple.json` which looks like this:
```json
{
	"id": "5a4f0254-35ec-4836-80d2-45f3148af5b6",
	"status": "active",
	"myData": [
		{
			"dataId": "e6831bfa-6f9f-4653-823f-318dabeba4c3",
			"dataStatus": "created"
		}
	]
}
```

I observed the following behavior:

- If I change the top level id to "37", I receive a validation error, that it is not a UUID. --> Expected behavior
- If I change the top level status to "test", I receive a validation error, that it must be a value of the defined enum. --> Expected behavior
- If I change the nested dataId to "37", I don't receive an error. --> I would expect that the CLI tells me that the dataId must be an UUID
- If I change the nested dataStatus to "test", I don't receive an error. --> I would expect that the CLI tells me that the value must be one of the enum

I used the datatcontract-cli in version 0.10.16.  If I define the types of the nested fields without using a definition than it works like expected.

Hints from issue comments:
@jochenchrist: Thanks for spotting and reporting this issue.@jochenchrist: This is now implemented with v0.10.18@k-werner: Wow thank you for the fast fix of that issue. With version 0.10.18 it works as expected

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab44653..af58756 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 ### Fixed
+- Definitions referenced by nested fields are not validated correctly (#595)
 - Replaced deprecated `primary` field with `primaryKey` in exporters, importers, examples, and Jinja templates for backward compatibility. Fixes [#518](https://github.com/your-repo/your-project/issues/518).
 - Cannot execute test on column of type record(bigquery) #597
 
diff --git a/datacontract/lint/resolve.py b/datacontract/lint/resolve.py
index 9d437b0..e51766b 100644
--- a/datacontract/lint/resolve.py
+++ b/datacontract/lint/resolve.py
@@ -54,20 +54,30 @@ def resolve_data_contract_from_location(
 def inline_definitions_into_data_contract(spec: DataContractSpecification):
     for model in spec.models.values():
         for field in model.fields.values():
-            # If ref_obj is not empty, we've already inlined definitions.
-            if not field.ref and not field.ref_obj:
-                continue
-
-            definition = _resolve_definition_ref(field.ref, spec)
-            field.ref_obj = definition
-
-            for field_name in field.model_fields.keys():
-                if field_name in definition.model_fields_set and field_name not in field.model_fields_set:
-                    setattr(field, field_name, getattr(definition, field_name))
-            # extras
-            for extra_field_name, extra_field_value in definition.model_extra.items():
-                if extra_field_name not in field.model_extra.keys():
-                    setattr(field, extra_field_name, extra_field_value)
+            inline_definition_into_field(field, spec)
+
+
+def inline_definition_into_field(field, spec):
+    # iterate recursively over arrays
+    if field.items is not None:
+        inline_definition_into_field(field.items, spec)
+
+    # iterate recursively over nested fields
+    if field.fields is not None:
+        for nested_field_name, nested_field in field.fields.items():
+            inline_definition_into_field(nested_field, spec)
+
+    if not field.ref:
+        return
+
+    definition = _resolve_definition_ref(field.ref, spec)
+    for field_name in field.model_fields.keys():
+        if field_name in definition.model_fields_set and field_name not in field.model_fields_set:
+            setattr(field, field_name, getattr(definition, field_name))
+    # extras
+    for extra_field_name, extra_field_value in definition.model_extra.items():
+        if extra_field_name not in field.model_extra.keys():
+            setattr(field, extra_field_name, extra_field_value)
 
 
 def _resolve_definition_ref(ref, spec) -> Definition:
diff --git a/datacontract/model/data_contract_specification.py b/datacontract/model/data_contract_specification.py
index 42600ac..19a2b83 100644
--- a/datacontract/model/data_contract_specification.py
+++ b/datacontract/model/data_contract_specification.py
@@ -141,7 +141,6 @@ class Quality(pyd.BaseModel):
 
 class Field(pyd.BaseModel):
     ref: str = pyd.Field(default=None, alias="$ref")
-    ref_obj: Definition = pyd.Field(default=None, exclude=True)
     title: str | None = None
     type: str = None
     format: str = None


Test patch:
diff --git a/tests/test_resolve.py b/tests/test_resolve.py
index 3b42105..56c1f9e 100644
--- a/tests/test_resolve.py
+++ b/tests/test_resolve.py
@@ -54,6 +54,64 @@ def test_resolve_data_contract_complex_definition():
     assert datacontract.models["orders"].fields["order_id"].type == "int"
 
 
+def test_resolve_data_contract_array_definition():
+    datacontract = resolve_data_contract(
+        data_contract_str="""
+    dataContractSpecification: 1.1.0
+    id: my-id
+    info:
+      title: My Title
+      version: 1.0.0
+    models:
+      my_message:
+        fields:
+          my_data:
+            type: array
+            items:
+              name: My Data
+              type: object
+              fields:
+                data_id:
+                  $ref: "#/definitions/order_id"
+                  required: true
+    definitions:
+      order_id:
+        name: order_id
+        type: int
+    """,
+        inline_definitions=True,
+    )
+    assert datacontract.models["my_message"].fields["my_data"].items.fields["data_id"].type == "int"
+
+
+def test_resolve_data_contract_nested_definition():
+    datacontract = resolve_data_contract(
+        data_contract_str="""
+    dataContractSpecification: 1.1.0
+    id: my-id
+    info:
+      title: My Title
+      version: 1.0.0
+    models:
+      my_message:
+        fields:
+          my_data:
+            name: My Data
+            type: object
+            fields:
+              data_id:
+                $ref: "#/definitions/order_id"
+                required: true
+    definitions:
+      order_id:
+        name: order_id
+        type: int
+    """,
+        inline_definitions=True,
+    )
+    assert datacontract.models["my_message"].fields["my_data"].fields["data_id"].type == "int"
+
+
 def test_resolve_data_contract_simple_definition_file():
     with tempfile.NamedTemporaryFile(delete=True) as temp_file:
         # create temp file with content