diff --git a/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj b/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj
index d3e7433..f1e24e3 100644
--- a/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj
+++ b/tests/SwaggerProvider.Tests/SwaggerProvider.Tests.fsproj
@@ -20,6 +20,7 @@
+
diff --git a/tests/SwaggerProvider.Tests/v3/Schema.DefinitionPathTests.fs b/tests/SwaggerProvider.Tests/v3/Schema.DefinitionPathTests.fs
new file mode 100644
index 0000000..ff0aa50
--- /dev/null
+++ b/tests/SwaggerProvider.Tests/v3/Schema.DefinitionPathTests.fs
@@ -0,0 +1,109 @@
+module SwaggerProvider.Tests.v3_Schema_DefinitionPathTests
+
+/// Unit tests for DefinitionPath.Parse — the function that splits a JSON Reference
+/// path (e.g. "#/components/schemas/My.Namespace.TypeName") into its namespace list,
+/// requested type name, and PascalCase candidate name.
+
+open SwaggerProvider.Internal.v3.Compilers
+open Xunit
+open FsUnitTyped
+
+// ── Prefix constant ───────────────────────────────────────────────────────────
+
+[]
+let ``DefinitionPrefix is the OpenAPI component schema reference prefix``() =
+ DefinitionPath.DefinitionPrefix |> shouldEqual "#/components/schemas/"
+
+// ── Simple (un-namespaced) names ──────────────────────────────────────────────
+
+[]
+let ``simple name has empty namespace``() =
+ let result = DefinitionPath.Parse "#/components/schemas/Pet"
+ result.Namespace |> shouldEqual []
+
+[]
+let ``simple name preserves RequestedTypeName exactly``() =
+ let result = DefinitionPath.Parse "#/components/schemas/Pet"
+ result.RequestedTypeName |> shouldEqual "Pet"
+
+[]
+let ``simple PascalCase name has matching ProvidedTypeNameCandidate``() =
+ let result = DefinitionPath.Parse "#/components/schemas/Pet"
+ result.ProvidedTypeNameCandidate |> shouldEqual "Pet"
+
+[]
+let ``simple camelCase name is PascalCased in ProvidedTypeNameCandidate``() =
+ let result = DefinitionPath.Parse "#/components/schemas/petModel"
+ result.ProvidedTypeNameCandidate |> shouldEqual "PetModel"
+
+[]
+let ``simple camelCase name preserves original casing in RequestedTypeName``() =
+ let result = DefinitionPath.Parse "#/components/schemas/petModel"
+ result.RequestedTypeName |> shouldEqual "petModel"
+
+// ── One-level namespaced names ────────────────────────────────────────────────
+
+[]
+let ``one-level namespace is extracted``() =
+ let result = DefinitionPath.Parse "#/components/schemas/My.Pet"
+ result.Namespace |> shouldEqual [ "My" ]
+
+[]
+let ``one-level namespace leaves type name after the dot``() =
+ let result = DefinitionPath.Parse "#/components/schemas/My.Pet"
+ result.RequestedTypeName |> shouldEqual "Pet"
+
+[]
+let ``one-level namespace applies PascalCase to ProvidedTypeNameCandidate``() =
+ let result = DefinitionPath.Parse "#/components/schemas/my.petModel"
+ result.ProvidedTypeNameCandidate |> shouldEqual "PetModel"
+
+// ── Multi-level namespaced names ──────────────────────────────────────────────
+
+[]
+let ``two-level namespace is fully extracted``() =
+ let result = DefinitionPath.Parse "#/components/schemas/A.B.TypeName"
+ result.Namespace |> shouldEqual [ "A"; "B" ]
+ result.RequestedTypeName |> shouldEqual "TypeName"
+
+[]
+let ``three-level namespace is fully extracted``() =
+ let result = DefinitionPath.Parse "#/components/schemas/A.B.C.TypeName"
+ result.Namespace |> shouldEqual [ "A"; "B"; "C" ]
+ result.RequestedTypeName |> shouldEqual "TypeName"
+
+[]
+let ``deep namespace preserves all namespace segments``() =
+ let result =
+ DefinitionPath.Parse "#/components/schemas/Com.Example.Api.Models.Response"
+
+ result.Namespace |> shouldEqual [ "Com"; "Example"; "Api"; "Models" ]
+ result.RequestedTypeName |> shouldEqual "Response"
+
+// ── Names containing non-alphanumeric / non-dot characters ───────────────────
+// Hyphens and underscores are valid in JSON schema names but are NOT dot-separators,
+// so the function should find no namespace when no dot precedes them.
+
+[]
+let ``name containing only a hyphen has no namespace``() =
+ let result = DefinitionPath.Parse "#/components/schemas/my-type"
+ result.Namespace |> shouldEqual []
+
+[]
+let ``name with hyphen does not extract a spurious namespace``() =
+ let result = DefinitionPath.Parse "#/components/schemas/Api.my-type"
+ // The dot before "my-type" is within the valid prefix; hyphen stops the scan,
+ // so LastIndexOf('.') finds the dot before "my-type".
+ result.Namespace |> shouldEqual [ "Api" ]
+
+// ── Error handling ────────────────────────────────────────────────────────────
+
+[]
+let ``definition not starting with prefix throws``() =
+ let act = fun () -> DefinitionPath.Parse "notADefinitionPath" |> ignore
+ act |> shouldFail
+
+[]
+let ``swagger 2 definitions path does not start with v3 prefix and throws``() =
+ let act = fun () -> DefinitionPath.Parse "#/definitions/Pet" |> ignore
+ act |> shouldFail
diff --git a/tests/SwaggerProvider.Tests/v3/Schema.TestHelpers.fs b/tests/SwaggerProvider.Tests/v3/Schema.TestHelpers.fs
index 18b0316..4e003f0 100644
--- a/tests/SwaggerProvider.Tests/v3/Schema.TestHelpers.fs
+++ b/tests/SwaggerProvider.Tests/v3/Schema.TestHelpers.fs
@@ -71,3 +71,51 @@ components:
propYaml
compileSchemaAndGetValueType schemaStr
+
+/// Compile a minimal v3 schema with configurable DefinitionCompiler options.
+/// Returns the .NET type of the `Value` property on `TestType`.
+let compilePropertyTypeWith (provideNullable: bool) (propYaml: string) (required: bool) : Type =
+ let settings = Microsoft.OpenApi.Reader.OpenApiReaderSettings()
+ settings.AddYamlReader()
+
+ let requiredBlock =
+ if required then
+ " required:\n - Value\n"
+ else
+ ""
+
+ let schemaStr =
+ sprintf
+ """openapi: "3.0.0"
+info:
+ title: TypeMappingTest
+ version: "1.0.0"
+paths: {}
+components:
+ schemas:
+ TestType:
+ type: object
+%s properties:
+ Value:
+%s"""
+ requiredBlock
+ propYaml
+
+ let readResult =
+ Microsoft.OpenApi.OpenApiDocument.Parse(schemaStr, settings = settings)
+
+ let schema =
+ match readResult.Document with
+ | null -> failwith "Failed to parse schema."
+ | doc -> doc
+
+ let defCompiler = DefinitionCompiler(schema, provideNullable, false)
+ let opCompiler = OperationCompiler(schema, defCompiler, true, false, false)
+ opCompiler.CompileProvidedClients(defCompiler.Namespace)
+
+ let types = defCompiler.Namespace.GetProvidedTypes()
+ let testType = types |> List.find(fun t -> t.Name = "TestType")
+
+ match testType.GetDeclaredProperty("Value") with
+ | null -> failwith "Property 'Value' not found on TestType"
+ | prop -> prop.PropertyType
diff --git a/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs b/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs
index 443bdc0..b7bf117 100644
--- a/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs
+++ b/tests/SwaggerProvider.Tests/v3/Schema.TypeMappingTests.fs
@@ -279,3 +279,41 @@ let ``optional allOf $ref to integer alias resolves to Option``() =
let ``optional allOf $ref to int64 alias resolves to Option``() =
let ty = compileAllOfRefType " type: integer\n format: int64\n" false
ty |> shouldEqual typeof
+
+// ── PreferNullable=true: optional value types use Nullable ─────────────────
+// When provideNullable=true, the DefinitionCompiler wraps optional value types
+// in Nullable instead of Option.
+
+[]
+let ``PreferNullable: optional boolean maps to Nullable``() =
+ let ty = compilePropertyTypeWith true " type: boolean\n" false
+
+ ty
+ |> shouldEqual(typedefof>.MakeGenericType(typeof))
+
+[]
+let ``PreferNullable: optional integer maps to Nullable``() =
+ let ty = compilePropertyTypeWith true " type: integer\n" false
+
+ ty
+ |> shouldEqual(typedefof>.MakeGenericType(typeof))
+
+[]
+let ``PreferNullable: optional int64 maps to Nullable``() =
+ let ty =
+ compilePropertyTypeWith true " type: integer\n format: int64\n" false
+
+ ty
+ |> shouldEqual(typedefof>.MakeGenericType(typeof))
+
+[]
+let ``PreferNullable: required integer is not wrapped (Nullable only for optional)``() =
+ let ty = compilePropertyTypeWith true " type: integer\n" true
+ ty |> shouldEqual typeof
+
+[]
+let ``PreferNullable: optional string is not wrapped (reference type)``() =
+ // Reference types like string are not wrapped in Nullable since they are
+ // already nullable by nature — same behaviour as Option mode.
+ let ty = compilePropertyTypeWith true " type: string\n" false
+ ty |> shouldEqual typeof