CVE-2020-15142 (published 2020-08-14T17:15Z)

In openapi-python-client before version 0.5.3, clients generated with a maliciously crafted OpenAPI Document can generate arbitrary Python code. Subsequent execution of this malicious client is arbitrary code execution.

Severity: MEDIUM
CVSS v2 base score: 6.0 (AV:N/AC:M/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 9.0, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/triaxtec/openapi-python-client
commit_details: repo_url: https://github.com/triaxtec/openapi-python-client
author: Ethan Mann
author_date: 2020-08-13 13:05:36-04:00
msg: Merge pull request from GHSA-9x4c-63pf-525f

* All strings used as file/directory names are now sanitized to address the path traversal vulnerabilities

* Switched calls to utils.spinal_case to utils.kebab_case

* Quotation marks are now escaped in all names/descriptions to address arbitrary code execution issues

* Sanitized identifiers are now also checked for keyword collision

* Added validation for default property values where possible

Address arbitrary code execution vulnerability

* Enum keys are now also sanitized

* Update end_to_end_tests/fastapi_app/__init__.py

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>

* Update openapi_python_client/parser/properties.py

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>

* Changed NotImplementedErrors to ValidationErrors

* Updated changelog

* Fixed regex

* Update openapi_python_client/utils.py

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>

* Fixed rendering of union, datetime, and date properties. Added e2e test for all defaults

* Fixed rendering of date/datetime default values when timezone is included

* Simplified datetime rendering

* Fixed typo

* Readded support for DictProperty defaults and fixed a bug with json body template rendering

* Fixed dict json body compatibility

Co-authored-by: Ethan Mann <emann@triaxtec.com>
Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>
repository: repo_url: https://github.com/triaxtec/openapi-python-client
repo_name: openapi-generators/openapi-python-client
description: Generate modern Python clients from OpenAPI
repo_language: Python
owner: triaxtec
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 
-## 0.5.3 - Unrelease
+## 0.5.3 - Unreleased
+### Security
+- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)
+- All values that get placed into python files (everything from enum names, to endpoint descriptions, to default values) are validated and/or saniziatied to address arbitrary code execution vulnerabilities (CVE-2020-15142)
+
+### Changes
+- Due to security concerns/implementation complexities, default values are temporarily unsupported for any `RefProperty` that doesn't refer to an enum.
+- Defaults for properties must now be valid values for their respective type (e.g. "example string" is an invalid default for an `integer` type property, and the function for an endpoint using it would fail to generate and be skipped). 
+
 ### Additions
 - Added support for header parameters (#117)
 

programming_language: Markdown
filename: __init__.py
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@ from enum import Enum
 from pathlib import Path
 from typing import Any, Dict, List, Union
 
-from fastapi import APIRouter, FastAPI, File, Header, Query, UploadFile
+from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
 from pydantic import BaseModel
 
 app = FastAPI(title="My Test API", description="An API for testing openapi-python-client",)
@@ -43,13 +43,15 @@ class AModel(BaseModel):
 
     an_enum_value: AnEnum
     nested_list_of_enums: List[List[DifferentEnum]] = []
-    some_dict: Dict[str, str] = {}
+    some_dict: Dict[str, str]
     aCamelDateTime: Union[datetime, date]
     a_date: date
 
 
 @test_router.get("/", response_model=List[AModel], operation_id="getUserList")
-def get_list(an_enum_value: List[AnEnum] = Query(...), some_date: Union[date, datetime] = Query(...)):
+def get_list(
+    an_enum_value: List[AnEnum] = Query(...), some_date: Union[date, datetime] = Query(...),
+):
     """ Get a list of things """
     return
 
@@ -67,6 +69,22 @@ def json_body(body: AModel):
     return
 
 
+@test_router.post("/test_defaults")
+def test_defaults(
+    string_prop: str = Query(default="the default string"),
+    datetime_prop: datetime = Query(default=datetime(1010, 10, 10)),
+    date_prop: date = Query(default=date(1010, 10, 10)),
+    float_prop: float = Query(default=3.14),
+    int_prop: int = Query(default=7),
+    boolean_prop: bool = Query(default=False),
+    list_prop: List[AnEnum] = Query(default=[AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE]),
+    union_prop: Union[float, str] = Query(default="not a float"),
+    enum_prop: AnEnum = Query(default=AnEnum.FIRST_VALUE),
+    dict_prop: Dict[str, str] = Body(default={"key": "val"}),
+):
+    return
+
+
 app.include_router(test_router, prefix="/tests", tags=["tests"])
 
 

programming_language: Python
filename: openapi.json
change_type: ModificationType.MODIFY
diff: @@ -184,6 +184,156 @@
                     }
                 }
             }
+        },
+        "/tests/test_defaults": {
+            "post": {
+                "tags": [
+                    "tests"
+                ],
+                "summary": "Test Defaults",
+                "operationId": "test_defaults_tests_test_defaults_post",
+                "parameters": [
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "String Prop",
+                            "type": "string",
+                            "default": "the default string"
+                        },
+                        "name": "string_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Datetime Prop",
+                            "type": "string",
+                            "format": "date-time",
+                            "default": "1010-10-10T00:00:00"
+                        },
+                        "name": "datetime_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Date Prop",
+                            "type": "string",
+                            "format": "date",
+                            "default": "1010-10-10"
+                        },
+                        "name": "date_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Float Prop",
+                            "type": "number",
+                            "default": 3.14
+                        },
+                        "name": "float_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Int Prop",
+                            "type": "integer",
+                            "default": 7
+                        },
+                        "name": "int_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Boolean Prop",
+                            "type": "boolean",
+                            "default": false
+                        },
+                        "name": "boolean_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "List Prop",
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/components/schemas/AnEnum"
+                            },
+                            "default": [
+                                "FIRST_VALUE",
+                                "SECOND_VALUE"
+                            ]
+                        },
+                        "name": "list_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Union Prop",
+                            "anyOf": [
+                                {
+                                    "type": "number"
+                                },
+                                {
+                                    "type": "string"
+                                }
+                            ],
+                            "default": "not a float"
+                        },
+                        "name": "union_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "$ref": "#/components/schemas/AnEnum"
+                        },
+                        "name": "enum_prop",
+                        "in": "query"
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "title": "Dict Prop",
+                                "type": "object",
+                                "additionalProperties": {
+                                    "type": "string"
+                                },
+                                "default": {
+                                    "key": "val"
+                                }
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {}
+                            }
+                        }
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        }
+                    }
+                }
+            }
         }
     },
     "components": {
@@ -192,6 +342,7 @@
                 "title": "AModel",
                 "required": [
                     "an_enum_value",
+                    "some_dict",
                     "aCamelDateTime",
                     "a_date"
                 ],
@@ -216,8 +367,7 @@
                         "type": "object",
                         "additionalProperties": {
                             "type": "string"
-                        },
-                        "default": {}
+                        }
                     },
                     "aCamelDateTime": {
                         "title": "Acameldatetime",

programming_language: Swift
filename: tests.py
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
-from dataclasses import asdict
-from datetime import date, datetime
+import datetime
+from dataclasses import asdict, field
 from typing import Any, Dict, List, Optional, Union, cast
 
 import httpx
@@ -13,7 +13,7 @@ from ..models.http_validation_error import HTTPValidationError
 
 
 def get_user_list(
-    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[date, datetime],
+    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime],
 ) -> Union[
     List[AModel], HTTPValidationError,
 ]:
@@ -29,7 +29,7 @@ def get_user_list(
 
         json_an_enum_value.append(an_enum_value_item)
 
-    if isinstance(some_date, date):
+    if isinstance(some_date, datetime.date):
         json_some_date = some_date.isoformat()
 
     else:
@@ -94,3 +94,81 @@ def json_body_tests_json_body_post(
         return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
     else:
         raise ApiResponseError(response=response)
+
+
+def test_defaults_tests_test_defaults_post(
+    *,
+    client: Client,
+    json_body: Dict[Any, Any],
+    string_prop: Optional[str] = "the default string",
+    datetime_prop: Optional[datetime.datetime] = datetime.datetime(1010, 10, 10, 0, 0),
+    date_prop: Optional[datetime.date] = datetime.date(1010, 10, 10),
+    float_prop: Optional[float] = 3.14,
+    int_prop: Optional[int] = 7,
+    boolean_prop: Optional[bool] = False,
+    list_prop: Optional[List[AnEnum]] = field(
+        default_factory=lambda: cast(Optional[List[AnEnum]], [AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE])
+    ),
+    union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
+    enum_prop: Optional[AnEnum] = None,
+) -> Union[
+    None, HTTPValidationError,
+]:
+
+    """  """
+    url = "{}/tests/test_defaults".format(client.base_url)
+
+    headers: Dict[str, Any] = client.get_headers()
+
+    json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None
+
+    json_date_prop = date_prop.isoformat() if date_prop else None
+
+    if list_prop is None:
+        json_list_prop = None
+    else:
+        json_list_prop = []
+        for list_prop_item_data in list_prop:
+            list_prop_item = list_prop_item_data.value
+
+            json_list_prop.append(list_prop_item)
+
+    if union_prop is None:
+        json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
+    elif isinstance(union_prop, float):
+        json_union_prop = union_prop
+    else:
+        json_union_prop = union_prop
+
+    json_enum_prop = enum_prop.value if enum_prop else None
+
+    params: Dict[str, Any] = {}
+    if string_prop is not None:
+        params["string_prop"] = string_prop
+    if datetime_prop is not None:
+        params["datetime_prop"] = json_datetime_prop
+    if date_prop is not None:
+        params["date_prop"] = json_date_prop
+    if float_prop is not None:
+        params["float_prop"] = float_prop
+    if int_prop is not None:
+        params["int_prop"] = int_prop
+    if boolean_prop is not None:
+        params["boolean_prop"] = boolean_prop
+    if list_prop is not None:
+        params["list_prop"] = json_list_prop
+    if union_prop is not None:
+        params["union_prop"] = json_union_prop
+    if enum_prop is not None:
+        params["enum_prop"] = json_enum_prop
+
+    json_json_body = json_body
+
+    response = httpx.post(url=url, headers=headers, json=json_json_body, params=params,)
+
+    if response.status_code == 200:
+        return None
+    if response.status_code == 422:
+        return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
+    else:
+        raise ApiResponseError(response=response)

programming_language: Python
filename: tests.py
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
-from dataclasses import asdict
-from datetime import date, datetime
+import datetime
+from dataclasses import asdict, field
 from typing import Any, Dict, List, Optional, Union, cast
 
 import httpx
@@ -13,7 +13,7 @@ from ..models.http_validation_error import HTTPValidationError
 
 
 async def get_user_list(
-    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[date, datetime],
+    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime],
 ) -> Union[
     List[AModel], HTTPValidationError,
 ]:
@@ -29,7 +29,7 @@ async def get_user_list(
 
         json_an_enum_value.append(an_enum_value_item)
 
-    if isinstance(some_date, date):
+    if isinstance(some_date, datetime.date):
         json_some_date = some_date.isoformat()
 
     else:
@@ -97,3 +97,82 @@ async def json_body_tests_json_body_post(
         return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
     else:
         raise ApiResponseError(response=response)
+
+
+async def test_defaults_tests_test_defaults_post(
+    *,
+    client: Client,
+    json_body: Dict[Any, Any],
+    string_prop: Optional[str] = "the default string",
+    datetime_prop: Optional[datetime.datetime] = datetime.datetime(1010, 10, 10, 0, 0),
+    date_prop: Optional[datetime.date] = datetime.date(1010, 10, 10),
+    float_prop: Optional[float] = 3.14,
+    int_prop: Optional[int] = 7,
+    boolean_prop: Optional[bool] = False,
+    list_prop: Optional[List[AnEnum]] = field(
+        default_factory=lambda: cast(Optional[List[AnEnum]], [AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE])
+    ),
+    union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
+    enum_prop: Optional[AnEnum] = None,
+) -> Union[
+    None, HTTPValidationError,
+]:
+
+    """  """
+    url = "{}/tests/test_defaults".format(client.base_url,)
+
+    headers: Dict[str, Any] = client.get_headers()
+
+    json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None
+
+    json_date_prop = date_prop.isoformat() if date_prop else None
+
+    if list_prop is None:
+        json_list_prop = None
+    else:
+        json_list_prop = []
+        for list_prop_item_data in list_prop:
+            list_prop_item = list_prop_item_data.value
+
+            json_list_prop.append(list_prop_item)
+
+    if union_prop is None:
+        json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
+    elif isinstance(union_prop, float):
+        json_union_prop = union_prop
+    else:
+        json_union_prop = union_prop
+
+    json_enum_prop = enum_prop.value if enum_prop else None
+
+    params: Dict[str, Any] = {}
+    if string_prop is not None:
+        params["string_prop"] = string_prop
+    if datetime_prop is not None:
+        params["datetime_prop"] = json_datetime_prop
+    if date_prop is not None:
+        params["date_prop"] = json_date_prop
+    if float_prop is not None:
+        params["float_prop"] = float_prop
+    if int_prop is not None:
+        params["int_prop"] = int_prop
+    if boolean_prop is not None:
+        params["boolean_prop"] = boolean_prop
+    if list_prop is not None:
+        params["list_prop"] = json_list_prop
+    if union_prop is not None:
+        params["union_prop"] = json_union_prop
+    if enum_prop is not None:
+        params["enum_prop"] = json_enum_prop
+
+    json_json_body = json_body
+
+    async with httpx.AsyncClient() as _client:
+        response = await _client.post(url=url, headers=headers, json=json_json_body, params=params,)
+
+    if response.status_code == 200:
+        return None
+    if response.status_code == 422:
+        return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
+    else:
+        raise ApiResponseError(response=response)

programming_language: Python
filename: a_model.py
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,7 @@
 from __future__ import annotations
 
+import datetime
 from dataclasses import dataclass, field
-from datetime import date, datetime
 from typing import Any, Dict, List, Optional, Union, cast
 
 from .an_enum import AnEnum
@@ -13,17 +13,19 @@ class AModel:
     """ A Model for testing all the ways custom objects can be used  """
 
     an_enum_value: AnEnum
-    a_camel_date_time: Union[datetime, date]
-    a_date: date
+    some_dict: Dict[Any, Any]
+    a_camel_date_time: Union[datetime.datetime, datetime.date]
+    a_date: datetime.date
     nested_list_of_enums: Optional[List[List[DifferentEnum]]] = field(
         default_factory=lambda: cast(Optional[List[List[DifferentEnum]]], [])
     )
-    some_dict: Optional[Dict[Any, Any]] = field(default_factory=lambda: cast(Optional[Dict[Any, Any]], {}))
 
     def to_dict(self) -> Dict[str, Any]:
         an_enum_value = self.an_enum_value.value
 
-        if isinstance(self.a_camel_date_time, datetime):
+        some_dict = self.some_dict
+
+        if isinstance(self.a_camel_date_time, datetime.datetime):
             a_camel_date_time = self.a_camel_date_time.isoformat()
 
         else:
@@ -44,35 +46,35 @@ class AModel:
 
                 nested_list_of_enums.append(nested_list_of_enums_item)
 
-        some_dict = self.some_dict
-
         return {
             "an_enum_value": an_enum_value,
+            "some_dict": some_dict,
             "aCamelDateTime": a_camel_date_time,
             "a_date": a_date,
             "nested_list_of_enums": nested_list_of_enums,
-            "some_dict": some_dict,
         }
 
     @staticmethod
     def from_dict(d: Dict[str, Any]) -> AModel:
         an_enum_value = AnEnum(d["an_enum_value"])
 
-        def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime, date]:
-            a_camel_date_time: Union[datetime, date]
+        some_dict = d["some_dict"]
+
+        def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime.datetime, datetime.date]:
+            a_camel_date_time: Union[datetime.datetime, datetime.date]
             try:
-                a_camel_date_time = datetime.fromisoformat(d["aCamelDateTime"])
+                a_camel_date_time = datetime.datetime.fromisoformat(d["aCamelDateTime"])
 
                 return a_camel_date_time
             except:
                 pass
-            a_camel_date_time = date.fromisoformat(d["aCamelDateTime"])
+            a_camel_date_time = datetime.date.fromisoformat(d["aCamelDateTime"])
 
             return a_camel_date_time
 
         a_camel_date_time = _parse_a_camel_date_time(d["aCamelDateTime"])
 
-        a_date = date.fromisoformat(d["a_date"])
+        a_date = datetime.date.fromisoformat(d["a_date"])
 
         nested_list_of_enums = []
         for nested_list_of_enums_item_data in d.get("nested_list_of_enums") or []:
@@ -84,12 +86,10 @@ class AModel:
 
             nested_list_of_enums.append(nested_list_of_enums_item)
 
-        some_dict = d.get("some_dict")
-
         return AModel(
             an_enum_value=an_enum_value,
+            some_dict=some_dict,
             a_camel_date_time=a_camel_date_time,
             a_date=a_date,
             nested_list_of_enums=nested_list_of_enums,
-            some_dict=some_dict,
         )

programming_language: Python
filename: __init__.py
change_type: ModificationType.MODIFY
diff: @@ -96,7 +96,9 @@ class Project:
 
         self.package_name: str = self.package_name_override or self.project_name.replace("-", "_")
         self.package_dir: Path = self.project_dir / self.package_name
-        self.package_description: str = f"A client library for accessing {self.openapi.title}"
+        self.package_description: str = utils.remove_string_escapes(
+            f"A client library for accessing {self.openapi.title}"
+        )
         self.version: str = openapi.version
 
         self.env.filters.update(self.TEMPLATE_FILTERS)

programming_language: Python
filename: errors.py
change_type: ModificationType.MODIFY
diff: @@ -37,3 +37,7 @@ class PropertyError(ParseError):
     """ Error raised when there's a problem creating a Property """
 
     header = "Problem creating a Property: "
+
+
+class ValidationError(Exception):
+    pass

programming_language: Python
filename: openapi.py
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Set, Union
 from pydantic import ValidationError
 
 from .. import schema as oai
+from .. import utils
 from .errors import GeneratorError, ParseError, PropertyError
 from .properties import EnumProperty, Property, property_from_data
 from .reference import Reference
@@ -182,7 +183,7 @@ class Endpoint:
         endpoint = Endpoint(
             path=path,
             method=method,
-            description=data.description,
+            description=utils.remove_string_escapes(data.description) if data.description else "",
             name=data.operationId,
             requires_security=bool(data.security),
             tag=tag,

programming_language: Python
filename: properties.py
change_type: ModificationType.MODIFY
diff: @@ -1,11 +1,12 @@
 from __future__ import annotations
 
 from dataclasses import InitVar, dataclass, field
+from datetime import date, datetime
 from typing import Any, ClassVar, Dict, Generic, List, Optional, Set, TypeVar, Union
 
 from .. import schema as oai
 from .. import utils
-from .errors import PropertyError
+from .errors import PropertyError, ValidationError
 from .reference import Reference
 
 
@@ -19,6 +20,9 @@ class Property:
             templates/property_templates and must contain two macros: construct and transform. Construct will be used to
             build this property from JSON data (a response from an API). Transform will be used to convert this property
             to JSON data (when sending a request to the API).
+
+    Raises:
+        ValidationError: Raised when the default value fails to be converted to the expected type
     """
 
     name: str
@@ -32,10 +36,16 @@ class Property:
 
     def __post_init__(self) -> None:
         self.python_name = utils.snake_case(self.name)
+        if self.default is not None:
+            self.default = self._validate_default(default=self.default)
+
+    def _validate_default(self, default: Any) -> Any:
+        """ Check that the default value is valid for the property's type + perform any necessary sanitization """
+        raise ValidationError
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
-        if self.required:
+        if self.required or no_optional:
             return self._type_string
         return f"Optional[{self._type_string}]"
 
@@ -74,10 +84,8 @@ class StringProperty(Property):
 
     _type_string: ClassVar[str] = "str"
 
-    def __post_init__(self) -> None:
-        super().__post_init__()
-        if self.default is not None:
-            self.default = f'"{self.default}"'
+    def _validate_default(self, default: Any) -> str:
+        return f'"{utils.remove_string_escapes(default)}"'
 
 
 @dataclass
@@ -86,7 +94,7 @@ class DateTimeProperty(Property):
     A property of type datetime.datetime
     """
 
-    _type_string: ClassVar[str] = "datetime"
+    _type_string: ClassVar[str] = "datetime.datetime"
     template: ClassVar[str] = "datetime_property.pyi"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
@@ -97,15 +105,23 @@ class DateTimeProperty(Property):
             prefix: A prefix to put before any relative (local) module names.
         """
         imports = super().get_imports(prefix=prefix)
-        imports.update({"from datetime import datetime", "from typing import cast"})
+        imports.update({"import datetime", "from typing import cast"})
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        for format_string in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"):
+            try:
+                return repr(datetime.strptime(default, format_string))
+            except (TypeError, ValueError):
+                continue
+        raise ValidationError
+
 
 @dataclass
 class DateProperty(Property):
     """ A property of type datetime.date """
 
-    _type_string: ClassVar[str] = "date"
+    _type_string: ClassVar[str] = "datetime.date"
     template: ClassVar[str] = "date_property.pyi"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
@@ -116,9 +132,15 @@ class DateProperty(Property):
             prefix: A prefix to put before any relative (local) module names.
         """
         imports = super().get_imports(prefix=prefix)
-        imports.update({"from datetime import date", "from typing import cast"})
+        imports.update({"import datetime", "from typing import cast"})
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        try:
+            return repr(date.fromisoformat(default))
+        except (TypeError, ValueError) as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class FileProperty(Property):
@@ -146,6 +168,12 @@ class FloatProperty(Property):
     default: Optional[float] = None
     _type_string: ClassVar[str] = "float"
 
+    def _validate_default(self, default: Any) -> float:
+        try:
+            return float(default)
+        except (TypeError, ValueError) as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class IntProperty(Property):
@@ -154,6 +182,12 @@ class IntProperty(Property):
     default: Optional[int] = None
     _type_string: ClassVar[str] = "int"
 
+    def _validate_default(self, default: Any) -> int:
+        try:
+            return int(default)
+        except (TypeError, ValueError) as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class BooleanProperty(Property):
@@ -161,6 +195,10 @@ class BooleanProperty(Property):
 
     _type_string: ClassVar[str] = "bool"
 
+    def _validate_default(self, default: Any) -> bool:
+        # no try/except needed as anything that comes from the initial load from json/yaml will be boolable
+        return bool(default)
+
 
 InnerProp = TypeVar("InnerProp", bound=Property)
 
@@ -172,14 +210,9 @@ class ListProperty(Property, Generic[InnerProp]):
     inner_property: InnerProp
     template: ClassVar[str] = "list_property.pyi"
 
-    def __post_init__(self) -> None:
-        super().__post_init__()
-        if self.default is not None:
-            self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
-
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
-        if self.required:
+        if self.required or no_optional:
             return f"List[{self.inner_property.get_type_string()}]"
         return f"Optional[List[{self.inner_property.get_type_string()}]]"
 
@@ -198,6 +231,16 @@ class ListProperty(Property, Generic[InnerProp]):
             imports.add("from typing import cast")
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        if not isinstance(default, list):
+            raise ValidationError()
+
+        default = list(map(self.inner_property._validate_default, default))
+        if isinstance(self.inner_property, RefProperty):  # Fix enums to use the actual value
+            default = str(default).replace("'", "")
+
+        return f"field(default_factory=lambda: cast({self.get_type_string()}, {default}))"
+
 
 @dataclass
 class UnionProperty(Property):
@@ -206,11 +249,11 @@ class UnionProperty(Property):
     inner_properties: List[Property]
     template: ClassVar[str] = "union_property.pyi"
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
         inner_types = [p.get_type_string() for p in self.inner_properties]
         inner_prop_string = ", ".join(inner_types)
-        if self.required:
+        if self.required or no_optional:
             return f"Union[{inner_prop_string}]"
         return f"Optional[Union[{inner_prop_string}]]"
 
@@ -227,6 +270,15 @@ class UnionProperty(Property):
         imports.add("from typing import Union")
         return imports
 
+    def _validate_default(self, default: Any) -> Any:
+        for property in self.inner_properties:
+            try:
+                val = property._validate_default(default)
+                return val
+            except ValidationError:
+                continue
+        raise ValidationError()
+
 
 _existing_enums: Dict[str, EnumProperty] = {}
 
@@ -242,7 +294,6 @@ class EnumProperty(Property):
     template: ClassVar[str] = "enum_property.pyi"
 
     def __post_init__(self, title: str) -> None:  # type: ignore
-        super().__post_init__()
         reference = Reference.from_ref(title)
         dedup_counter = 0
         while reference.class_name in _existing_enums:
@@ -253,9 +304,7 @@ class EnumProperty(Property):
             reference = Reference.from_ref(f"{reference.class_name}{dedup_counter}")
 
         self.reference = reference
-        inverse_values = {v: k for k, v in self.values.items()}
-        if self.default is not None:
-            self.default = f"{self.reference.class_name}.{inverse_values[self.default]}"
+        super().__post_init__()
         _existing_enums[self.reference.class_name] = self
 
     @staticmethod
@@ -268,10 +317,10 @@ class EnumProperty(Property):
         """ Get all the EnumProperties that have been registered keyed by class name """
         return _existing_enums.get(name)
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
 
-        if self.required:
+        if self.required or no_optional:
             return self.reference.class_name
         return f"Optional[{self.reference.class_name}]"
 
@@ -298,10 +347,18 @@ class EnumProperty(Property):
                 key = f"VALUE_{i}"
             if key in output:
                 raise ValueError(f"Duplicate key {key} in Enum")
-            output[key] = value
+            sanitized_key = utils.fix_keywords(utils.sanitize(key))
+            output[sanitized_key] = utils.remove_string_escapes(value)
 
         return output
 
+    def _validate_default(self, default: Any) -> str:
+        inverse_values = {v: k for k, v in self.values.items()}
+        try:
+            return f"{self.reference.class_name}.{inverse_values[default]}"
+        except KeyError as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class RefProperty(Property):
@@ -316,9 +373,9 @@ class RefProperty(Property):
             return "enum_property.pyi"
         return "ref_property.pyi"
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
-        if self.required:
+        if self.required or no_optional:
             return self.reference.class_name
         return f"Optional[{self.reference.class_name}]"
 
@@ -339,17 +396,20 @@ class RefProperty(Property):
         )
         return imports
 
+    def _validate_default(self, default: Any) -> Any:
+        enum = EnumProperty.get_enum(self.reference.class_name)
+        if enum:
+            return enum._validate_default(default)
+        else:
+            raise ValidationError
+
 
 @dataclass
 class DictProperty(Property):
     """ Property that is a general Dict """
 
     _type_string: ClassVar[str] = "Dict[Any, Any]"
-
-    def __post_init__(self) -> None:
-        super().__post_init__()
-        if self.default is not None:
-            self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
+    template: ClassVar[str] = "dict_property.pyi"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -365,6 +425,11 @@ class DictProperty(Property):
             imports.add("from typing import cast")
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        if isinstance(default, dict):
+            return repr(default)
+        raise ValidationError
+
 
 def _string_based_property(
     name: str, required: bool, data: oai.Schema
@@ -381,10 +446,11 @@ def _string_based_property(
         return StringProperty(name=name, default=data.default, required=required, pattern=data.pattern)
 
 
-def property_from_data(
+def _property_from_data(
     name: str, required: bool, data: Union[oai.Reference, oai.Schema]
 ) -> Union[Property, PropertyError]:
     """ Generate a Property from the OpenAPI dictionary representation of it """
+    name = utils.remove_string_escapes(name)
     if isinstance(data, oai.Reference):
         return RefProperty(name=name, required=required, reference=Reference.from_ref(data.ref), default=None)
     if data.enum:
@@ -423,3 +489,12 @@ def property_from_data(
     elif data.type == "object":
         return DictProperty(name=name, required=required, default=data.default)
     return PropertyError(data=data, detail=f"unknown type {data.type}")
+
+
+def property_from_data(
+    name: str, required: bool, data: Union[oai.Reference, oai.Schema]
+) -> Union[Property, PropertyError]:
+    try:
+        return _property_from_data(name=name, required=required, data=data)
+    except ValidationError:
+        return PropertyError(detail="Failed to validate default value", data=data)

programming_language: Python
filename: async_endpoint_module.pyi
change_type: ModificationType.MODIFY
diff: @@ -70,7 +70,7 @@ async def {{ endpoint.name | snakecase }}(
             files=multipart_data.to_dict(),
             {% endif %}
             {% if endpoint.json_body %}
-            json={{ "json_" +  endpoint.json_body.python_name }},
+            json={{ "json_" + endpoint.json_body.python_name }},
             {% endif %}
             {% if endpoint.query_parameters %}
             params=params,

programming_language: HTML
filename: date_property.pyi
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,10 @@
 {% macro construct(property, source) %}
 {% if property.required %}
-{{ property.python_name }} = date.fromisoformat({{ source }})
+{{ property.python_name }} = datetime.date.fromisoformat({{ source }})
 {% else %}
 {{ property.python_name }} = None
 if {{ source }} is not None:
-    {{ property.python_name }} = date.fromisoformat(cast(str, {{ source }}))
+    {{ property.python_name }} = datetime.date.fromisoformat(cast(str, {{ source }}))
 {% endif %}
 {% endmacro %}
 

programming_language: HTML
filename: datetime_property.pyi
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,10 @@
 {% macro construct(property, source) %}
 {% if property.required %}
-{{ property.python_name }} = datetime.fromisoformat({{ source }})
+{{ property.python_name }} = datetime.datetime.fromisoformat({{ source }})
 {% else %}
 {{ property.python_name }} = None
 if {{ source }} is not None:
-    {{ property.python_name }} = datetime.fromisoformat(cast(str, {{ source }}))
+    {{ property.python_name }} = datetime.datetime.fromisoformat(cast(str, {{ source }}))
 {% endif %}
 {% endmacro %}
 

programming_language: HTML
filename: dict_property.pyi
change_type: ModificationType.ADD
diff: @@ -0,0 +1,17 @@
+{% macro construct(property, source) %}
+{% if property.required %}
+{{ property.python_name }} = {{ source }}
+{% else %}
+{{ property.python_name }} = None
+if {{ source }} is not None:
+    {{ property.python_name }} = {{ source }}
+{% endif %}
+{% endmacro %}
+
+{% macro transform(property, source, destination) %}
+{% if property.required %}
+{{ destination }} = {{ source }}
+{% else %}
+{{ destination }} = {{ source }} if {{ source }} else None
+{% endif %}
+{% endmacro %}

programming_language: HTML
filename: union_property.pyi
change_type: ModificationType.MODIFY
diff: @@ -24,13 +24,13 @@ def _parse_{{ property.python_name }}(data: Dict[str, Any]) -> {{ property.get_t
 {% macro transform(property, source, destination) %}
 {% if not property.required %}
 if {{ source }} is None:
-    {{ destination }} = None
+    {{ destination }}: {{ property.get_type_string() }} = None
 {% endif %}
 {% for inner_property in property.inner_properties %}
     {% if loop.first and property.required %}{# No if None statement before this #}
-if isinstance({{ source }}, {{ inner_property.get_type_string() }}):
+if isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
     {% elif not loop.last %}
-elif isinstance({{ source }}, {{ inner_property.get_type_string() }}):
+elif isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
     {% else %}
 else:
     {% endif %}

programming_language: HTML
filename: utils.py
change_type: ModificationType.MODIFY
diff: @@ -1,25 +1,36 @@
 import re
+from keyword import iskeyword
 
 import stringcase
 
 
-def _sanitize(value: str) -> str:
-    return re.sub(r"[^\w _-]+", "", value)
+def sanitize(value: str) -> str:
+    return re.sub(r"[^\w _\-]+", "", value)
+
+
+def fix_keywords(value: str) -> str:
+    if iskeyword(value):
+        return f"{value}_"
+    return value
 
 
 def group_title(value: str) -> str:
-    value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ -_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
+    value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ \-_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
     value = re.sub(r"(^|[ _-])([A-Z])", lambda m: m.group(1) + m.group(2).lower(), value)
     return value
 
 
 def snake_case(value: str) -> str:
-    return stringcase.snakecase(group_title(_sanitize(value)))
+    return fix_keywords(stringcase.snakecase(group_title(sanitize(value))))
 
 
 def pascal_case(value: str) -> str:
-    return stringcase.pascalcase(_sanitize(value))
+    return fix_keywords(stringcase.pascalcase(sanitize(value)))
 
 
 def kebab_case(value: str) -> str:
-    return stringcase.spinalcase(group_title(_sanitize(value)))
+    return fix_keywords(stringcase.spinalcase(group_title(sanitize(value))))
+
+
+def remove_string_escapes(value: str) -> str:
+    return value.replace('"', r"\"")

programming_language: Python
filename: test_openapi.py
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,7 @@ from pydantic import ValidationError
 from pydantic.error_wrappers import ErrorWrapper
 
 import openapi_python_client.schema as oai
-from openapi_python_client import GeneratorError
+from openapi_python_client import GeneratorError, utils
 from openapi_python_client.parser.errors import ParseError
 
 MODULE_NAME = "openapi_python_client.parser.openapi"
@@ -546,6 +546,8 @@ class TestEndpoint:
             responses=mocker.MagicMock(),
         )
 
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description)
+
         endpoint = Endpoint.from_data(data=data, path=path, method=method, tag="default")
 
         assert endpoint == _add_body.return_value

programming_language: Python
filename: test_properties.py
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,26 @@
+from datetime import date, datetime
+
 import pytest
 
 import openapi_python_client.schema as oai
-from openapi_python_client.parser.errors import PropertyError
+from openapi_python_client.parser.errors import PropertyError, ValidationError
+from openapi_python_client.parser.reference import Reference
 
 MODULE_NAME = "openapi_python_client.parser.properties"
 
 
 class TestProperty:
+    def test___post_init(self, mocker):
+        from openapi_python_client.parser.properties import Property
+
+        validate_default = mocker.patch(f"{MODULE_NAME}.Property._validate_default")
+
+        Property(name="a name", required=True, default=None)
+        validate_default.assert_not_called()
+
+        Property(name="a name", required=True, default="the default value")
+        validate_default.assert_called_with(default="the default value")
+
     def test_get_type_string(self):
         from openapi_python_client.parser.properties import Property
 
@@ -43,15 +57,20 @@ class TestProperty:
         p.required = False
         assert p.get_imports(prefix="") == {"from typing import Optional"}
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import Property
 
-class TestStringProperty:
-    def test___post_init__(self):
-        from openapi_python_client.parser.properties import StringProperty
+        # should be okay if default isn't specified
+        p = Property(name="a name", required=True, default=None)
 
-        sp = StringProperty(name="test", required=True, default="A Default Value",)
+        with pytest.raises(ValidationError):
+            p._validate_default("a default value")
 
-        assert sp.default == '"A Default Value"'
+        with pytest.raises(ValidationError):
+            p = Property(name="a name", required=True, default="")
 
+
+class TestStringProperty:
     def test_get_type_string(self):
         from openapi_python_client.parser.properties import StringProperty
 
@@ -61,6 +80,12 @@ class TestStringProperty:
         p.required = False
         assert p.get_type_string() == "Optional[str]"
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import StringProperty
+
+        p = StringProperty(name="a name", required=True, default="the default value")
+        assert p.default == '"the default value"'
+
 
 class TestDateTimeProperty:
     def test_get_imports(self, mocker):
@@ -70,17 +95,26 @@ class TestDateTimeProperty:
         mocker.patch("openapi_python_client.utils.snake_case")
         p = DateTimeProperty(name=name, required=True, default=None)
         assert p.get_imports(prefix="") == {
-            "from datetime import datetime",
+            "import datetime",
             "from typing import cast",
         }
 
         p.required = False
         assert p.get_imports(prefix="") == {
             "from typing import Optional",
-            "from datetime import datetime",
+            "import datetime",
             "from typing import cast",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import DateTimeProperty
+
+        with pytest.raises(ValidationError):
+            p = DateTimeProperty(name="a name", required=True, default="not a datetime")
+
+        p = DateTimeProperty(name="a name", required=True, default="2017-07-21T17:32:28Z")
+        assert p.default == "datetime.datetime(2017, 7, 21, 17, 32, 28, tzinfo=datetime.timezone.utc)"
+
 
 class TestDateProperty:
     def test_get_imports(self, mocker):
@@ -90,17 +124,26 @@ class TestDateProperty:
         mocker.patch("openapi_python_client.utils.snake_case")
         p = DateProperty(name=name, required=True, default=None)
         assert p.get_imports(prefix="") == {
-            "from datetime import date",
+            "import datetime",
             "from typing import cast",
         }
 
         p.required = False
         assert p.get_imports(prefix="") == {
             "from typing import Optional",
-            "from datetime import date",
+            "import datetime",
             "from typing import cast",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import DateProperty
+
+        with pytest.raises(ValidationError):
+            p = DateProperty(name="a name", required=True, default="not a date")
+
+        p = DateProperty(name="a name", required=True, default="1010-10-10")
+        assert p.default == "datetime.date(1010, 10, 10)"
+
 
 class TestFileProperty:
     def test_get_imports(self, mocker):
@@ -119,6 +162,54 @@ class TestFileProperty:
             "from dataclasses import astuple",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import FileProperty
+
+        # should be okay if default isn't specified
+        p = FileProperty(name="a name", required=True, default=None)
+
+        with pytest.raises(ValidationError):
+            p = FileProperty(name="a name", required=True, default="")
+
+
+class TestFloatProperty:
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import FloatProperty
+
+        # should be okay if default isn't specified
+        p = FloatProperty(name="a name", required=True, default=None)
+
+        p = FloatProperty(name="a name", required=True, default="123.123")
+        assert p.default == 123.123
+
+        with pytest.raises(ValidationError):
+            p = FloatProperty(name="a name", required=True, default="not a float")
+
+
+class TestIntProperty:
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import IntProperty
+
+        # should be okay if default isn't specified
+        p = IntProperty(name="a name", required=True, default=None)
+
+        p = IntProperty(name="a name", required=True, default="123")
+        assert p.default == 123
+
+        with pytest.raises(ValidationError):
+            p = IntProperty(name="a name", required=True, default="not an int")
+
+
+class TestBooleanProperty:
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import BooleanProperty
+
+        # should be okay if default isn't specified
+        p = BooleanProperty(name="a name", required=True, default=None)
+
+        p = BooleanProperty(name="a name", required=True, default="Literally anything will work")
+        assert p.default == True
+
 
 class TestListProperty:
     def test_get_type_string(self, mocker):
@@ -165,6 +256,30 @@ class TestListProperty:
             "from dataclasses import field",
         }
 
+    def test__validate_default(self, mocker):
+        from openapi_python_client.parser.properties import ListProperty
+
+        inner_property = mocker.MagicMock()
+        inner_type_string = mocker.MagicMock()
+        inner_property.get_type_string.return_value = inner_type_string
+        inner_property._validate_default.return_value = "y"
+
+        p = ListProperty(name="a name", required=True, default=["x"], inner_property=inner_property)
+        assert p.default == f"field(default_factory=lambda: cast(List[{inner_type_string}], ['y']))"
+
+        with pytest.raises(ValidationError):
+            p = ListProperty(name="a name", required=True, default="x", inner_property=inner_property)
+
+    def test__validate_default_enum_items(self, mocker):
+        from openapi_python_client.parser.properties import ListProperty, RefProperty
+
+        inner_enum_property = mocker.MagicMock(spec=RefProperty)
+        inner_enum_property.get_type_string.return_value = "AnEnum"
+        inner_enum_property._validate_default.return_value = "AnEnum.val1"
+
+        p = ListProperty(name="a name", required=True, default=["val1"], inner_property=inner_enum_property)
+        assert p.default == f"field(default_factory=lambda: cast(List[AnEnum], [AnEnum.val1]))"
+
 
 class TestUnionProperty:
     def test_get_type_string(self, mocker):
@@ -209,6 +324,28 @@ class TestUnionProperty:
             "from typing import Optional",
         }
 
+    def test__validate_default(self, mocker):
+        from openapi_python_client.parser.properties import UnionProperty
+
+        inner_property_1 = mocker.MagicMock()
+        inner_property_1.get_type_string.return_value = "inner_type_string_1"
+        inner_property_1._validate_default.side_effect = ValidationError()
+        inner_property_2 = mocker.MagicMock()
+        inner_property_2.get_type_string.return_value = "inner_type_string_2"
+        inner_property_2._validate_default.return_value = "the default value"
+        p = UnionProperty(
+            name="test", required=True, default="a value", inner_properties=[inner_property_1, inner_property_2]
+        )
+
+        assert p.default == "the default value"
+
+        inner_property_2._validate_default.side_effect = ValidationError()
+
+        with pytest.raises(ValidationError):
+            p = UnionProperty(
+                name="test", required=True, default="a value", inner_properties=[inner_property_1, inner_property_2]
+            )
+
 
 class TestEnumProperty:
     def test___post_init__(self, mocker):
@@ -327,6 +464,24 @@ class TestEnumProperty:
         assert properties.EnumProperty.get_enum("test") == "an enum"
         properties._existing_enums = {}
 
+    def test__validate_default(self, mocker):
+        fake_reference = mocker.MagicMock(class_name="MyTestEnum", module_name="my_test_enum")
+        mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)
+
+        from openapi_python_client.parser import properties
+
+        enum_property = properties.EnumProperty(
+            name="test", required=True, default="test", values={"TEST": "test"}, title="a_title"
+        )
+        assert enum_property.default == "MyTestEnum.TEST"
+
+        with pytest.raises(ValidationError):
+            enum_property = properties.EnumProperty(
+                name="test", required=True, default="bad_val", values={"TEST": "test"}, title="a_title"
+            )
+
+        properties._existing_enums = {}
+
 
 class TestRefProperty:
     def test_template(self, mocker):
@@ -376,14 +531,20 @@ class TestRefProperty:
             "from typing import Optional",
         }
 
+    def test__validate_default(self, mocker):
+        from openapi_python_client.parser.properties import RefProperty
+
+        with pytest.raises(ValidationError):
+            p = RefProperty(name="a name", required=True, default="", reference=mocker.MagicMock())
 
-class TestDictProperty:
-    def test___post_init__(self):
-        from openapi_python_client.parser.properties import DictProperty
+        enum_property = mocker.MagicMock()
+        enum_property._validate_default.return_value = "val1"
+        mocker.patch(f"{MODULE_NAME}.EnumProperty.get_enum", return_value=enum_property)
+        p = RefProperty(name="a name", required=True, default="", reference=mocker.MagicMock())
+        assert p.default == "val1"
 
-        p = DictProperty(name="blah", required=True, default={})
-        assert p.default == "field(default_factory=lambda: cast(Dict[Any, Any], {}))"
 
+class TestDictProperty:
     def test_get_imports(self, mocker):
         from openapi_python_client.parser.properties import DictProperty
 
@@ -409,6 +570,14 @@ class TestDictProperty:
             "from dataclasses import field",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import DictProperty
+
+        p = DictProperty(name="a name", required=True, default={"key": "value"})
+
+        with pytest.raises(ValidationError):
+            p = DictProperty(name="a name", required=True, default="not a dict")
+
 
 class TestPropertyFromData:
     def test_property_from_data_enum(self, mocker):
@@ -416,6 +585,7 @@ class TestPropertyFromData:
         required = mocker.MagicMock()
         data = mocker.MagicMock(title=None)
         EnumProperty = mocker.patch(f"{MODULE_NAME}.EnumProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
@@ -443,6 +613,7 @@ class TestPropertyFromData:
         data = oai.Reference.construct(ref=mocker.MagicMock())
         from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
         RefProperty = mocker.patch(f"{MODULE_NAME}.RefProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
@@ -457,6 +628,8 @@ class TestPropertyFromData:
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema.construct(type="string")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
+
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
@@ -478,6 +651,7 @@ class TestPropertyFromData:
         required = mocker.MagicMock()
         data = oai.Schema.construct(type=openapi_type)
         clazz = mocker.patch(f"{MODULE_NAME}.{python_type}")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
@@ -501,12 +675,13 @@ class TestPropertyFromData:
         data = oai.Schema(type="array", items={"type": "number", "default": "0.0"},)
         ListProperty = mocker.patch(f"{MODULE_NAME}.ListProperty")
         FloatProperty = mocker.patch(f"{MODULE_NAME}.FloatProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
-        FloatProperty.assert_called_once_with(name=f"{name}_item", required=True, default="0.0")
+        FloatProperty.assert_called_once_with(name=name, required=True, default="0.0")
         ListProperty.assert_called_once_with(
             name=name, required=required, default=None, inner_property=FloatProperty.return_value
         )
@@ -527,6 +702,7 @@ class TestPropertyFromData:
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema(type="array", items={},)
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
@@ -541,6 +717,7 @@ class TestPropertyFromData:
         UnionProperty = mocker.patch(f"{MODULE_NAME}.UnionProperty")
         FloatProperty = mocker.patch(f"{MODULE_NAME}.FloatProperty")
         IntProperty = mocker.patch(f"{MODULE_NAME}.IntProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
@@ -560,6 +737,7 @@ class TestPropertyFromData:
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema(anyOf=[{}])
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
@@ -588,6 +766,17 @@ class TestPropertyFromData:
             data=data, detail="Schemas must either have one of enum, anyOf, or type defined."
         )
 
+    def test_property_from_data_validation_error(self, mocker):
+        from openapi_python_client.parser.errors import PropertyError
+        from openapi_python_client.parser.properties import property_from_data
+
+        mocker.patch(f"{MODULE_NAME}._property_from_data").side_effect = ValidationError()
+
+        data = oai.Schema()
+        assert property_from_data(name="blah", required=True, data=data) == PropertyError(
+            detail="Failed to validate default value", data=data
+        )
+
 
 class TestStringBasedProperty:
     def test__string_based_property_no_format(self, mocker):

programming_language: Python
filename: test_utils.py
change_type: ModificationType.MODIFY
diff: @@ -22,3 +22,15 @@ def test_snake_case_from_camel():
 
 def test_kebab_case():
     assert utils.kebab_case("keep_alive") == "keep-alive"
+
+
+def test__sanitize():
+    assert utils.sanitize("something*~with lots_- of weird things}=") == "somethingwith lots_- of weird things"
+
+
+def test_no_string_escapes():
+    assert utils.remove_string_escapes('an "evil" string') == 'an \\"evil\\" string'
+
+
+def test__fix_keywords():
+    assert utils.fix_keywords("None") == "None_"

programming_language: Python