Repository: unionai-oss/pandera

Issue:
Index validation ignored in SeriesSchema
**Describe the bug**
Index validation is ignored when validating a Series.

- [x] I have checked that this issue has not already been reported.
- [x] I have confirmed this bug exists on the latest version of pandera.


#### Example

```python
import pandas as pd
import pandera as pa

SeriesSchema=pa.SeriesSchema(
    index=pa.Index(
        float,
        checks=[
            pa.Check.greater_than(10)
        ]
    )
)

srs1 = pd.Series([1,2,3,4], index=[0.1, 10.1, 20.1, 30.1])
srs2 = pd.Series([1,2,3,4], index=["a", "b", "c", "d"])

SeriesSchema.validate(srs1)
SeriesSchema.validate(srs2)

```

#### Expected behavior
Both validation checks should fail. The first should fail because the index is not uniformly greater than 10. The second should fail because the index is not a float.

#### Desktop (please complete the following information):

 - OS: Red Hat 8.5.0-18
 - Python: 3.9.16
 - Pandas: 2.2.1
 - Pandera: 0.18.0

#### Screenshots
![image](https://github.com/unionai-oss/pandera/assets/4600162/c574d2ff-3202-4423-931d-5ef45ebb4add)


Discussion:

Resolving patch:
diff --git a/pandera/api/pandas/array.py b/pandera/api/pandas/array.py
--- a/pandera/api/pandas/array.py
+++ b/pandera/api/pandas/array.py
@@ -469,6 +469,16 @@ def validate(  # type: ignore [override]
             lazy=lazy,
             inplace=inplace,
         )
+        if self.index is not None:
+            validated_obj = self.index.validate(
+                check_obj,
+                head=head,
+                tail=tail,
+                sample=sample,
+                random_state=random_state,
+                lazy=lazy,
+                inplace=inplace,
+            )
         return cast(pd.Series, validated_obj)
 
     def example(self, size=None) -> pd.Series:
diff --git a/pandera/backends/pandas/__init__.py b/pandera/backends/pandas/__init__.py
--- a/pandera/backends/pandas/__init__.py
+++ b/pandera/backends/pandas/__init__.py
@@ -72,6 +72,7 @@
 for t in series_datatypes:
     SeriesSchema.register_backend(t, SeriesSchemaBackend)
     Column.register_backend(t, ColumnBackend)
+    MultiIndex.register_backend(t, MultiIndexBackend)
     Index.register_backend(t, IndexBackend)
 
 for t in index_datatypes:
diff --git a/pandera/backends/pandas/components.py b/pandera/backends/pandas/components.py
--- a/pandera/backends/pandas/components.py
+++ b/pandera/backends/pandas/components.py
@@ -275,19 +275,21 @@ def validate(
                 reason_code=SchemaErrorReason.MISMATCH_INDEX,
             )
 
+        error_handler = ErrorHandler(lazy)
+
         if schema.coerce:
-            check_obj.index = schema.coerce_dtype(check_obj.index)
-            obj_to_validate = schema.dtype.coerce(
-                check_obj.index.to_series().reset_index(drop=True)
-            )
-        else:
-            obj_to_validate = check_obj.index.to_series().reset_index(
-                drop=True
-            )
+            try:
+                check_obj.index = schema.coerce_dtype(check_obj.index)
+            except SchemaError as exc:
+                error_handler.collect_error(
+                    validation_type(exc.reason_code),
+                    exc.reason_code,
+                    exc,
+                )
 
-        assert is_field(
-            super().validate(
-                obj_to_validate,
+        try:
+            _validated_obj = super().validate(
+                check_obj.index.to_series().reset_index(drop=True),
                 schema,
                 head=head,
                 tail=tail,
@@ -295,8 +297,24 @@ def validate(
                 random_state=random_state,
                 lazy=lazy,
                 inplace=inplace,
-            ),
-        )
+            )
+            assert is_field(_validated_obj)
+        except SchemaError as exc:
+            error_handler.collect_error(
+                validation_type(exc.reason_code),
+                exc.reason_code,
+                exc,
+            )
+        except SchemaErrors as exc:
+            error_handler.collect_errors(exc.schema_errors, exc)
+
+        if lazy and error_handler.collected_errors:
+            raise SchemaErrors(
+                schema=schema,
+                schema_errors=error_handler.schema_errors,
+                data=check_obj,
+            )
+
         return check_obj
 
 


Test patch:
diff --git a/tests/core/test_schemas.py b/tests/core/test_schemas.py
--- a/tests/core/test_schemas.py
+++ b/tests/core/test_schemas.py
@@ -667,6 +667,27 @@ def test_series_schema_with_index(coerce: bool) -> None:
     assert (validated_series_multiindex.index == multi_index).all()
 
 
+def test_series_schema_with_index_errors() -> None:
+    """Test that SeriesSchema raises errors for invalid index."""
+    schema_with_index = SeriesSchema(dtype=int, index=Index(int))
+    data = pd.Series([1, 2, 3], index=[1.0, 2.0, 3.0])
+    with pytest.raises(errors.SchemaError):
+        schema_with_index(data)
+
+    schema_with_index_check = SeriesSchema(
+        dtype=int, index=Index(float, Check(lambda x: x == 1.0))
+    )
+    with pytest.raises(errors.SchemaError):
+        schema_with_index_check(data)
+
+    schema_with_index_coerce = SeriesSchema(
+        dtype=int, index=Index(int, coerce=True)
+    )
+    expected = pd.Series([1, 2, 3], index=[1, 2, 3])
+    schema_with_index_coerce(data)
+    assert schema_with_index_coerce(data).equals(expected)
+
+
 class SeriesGreaterCheck:
     # pylint: disable=too-few-public-methods
     """Class creating callable objects to check if series elements exceed a
@@ -1622,9 +1643,9 @@ def test_lazy_dataframe_unique() -> None:
             Index(str, checks=Check.isin(["a", "b", "c"])),
             pd.DataFrame({"col": [1, 2, 3]}, index=["a", "b", "d"]),
             {
-                # expect that the data in the SchemaError is the pd.Index cast
-                # into a Series
-                "data": pd.Series(["a", "b", "d"]),
+                "data": pd.DataFrame(
+                    {"col": [1, 2, 3]}, index=["a", "b", "d"]
+                ),
                 "schema_errors": {
                     "Index": {"isin(['a', 'b', 'c'])": ["d"]},
                 },
@@ -1645,8 +1666,6 @@ def test_lazy_dataframe_unique() -> None:
                 ),
             ),
             {
-                # expect that the data in the SchemaError is the pd.MultiIndex
-                # cast into a DataFrame
                 "data": pd.DataFrame(
                     {"column": [1, 2, 3]},
                     index=pd.MultiIndex.from_arrays(
@@ -1724,12 +1743,12 @@ def fail_without_msg(data):
 @pytest.mark.parametrize(
     "from_dtype,to_dtype",
     [
-        # [float, int],
-        # [int, float],
-        # [object, int],
-        # [object, float],
+        [float, int],
+        [int, float],
+        [object, int],
+        [object, float],
         [int, object],
-        # [float, object],
+        [float, object],
     ],
 )
 def test_schema_coerce_inplace_validation(