This is an automated email from the ASF dual-hosted git repository.

Cole-Greer pushed a commit to branch simplePDT
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit fdff7b20a3e7849e93fcd1811e542455c28df1c3
Author: Cole Greer <[email protected]>
AuthorDate: Wed Jun 24 20:59:47 2026 -0700

    Add PrimitivePDT support to gremlin-dotnet GLV
    
    Implements PrimitivePDT in the .NET GLV, mirroring composite support and
    applying the review lessons from the Python GLV.
    
    - PrimitiveProviderDefinedType (Name, Value) + IPrimitivePdtAdapter<T>
      (TypeName/FromString/ToString) in Structure/.
    - DataType.PrimitivePDT (0xF1) enabled; PrimitivePDTSerializer writes/reads
      two fully-qualified Strings; registered in TypeSerializerRegistry.
    - ProviderDefinedTypeRegistry gains an explicit primitive adapter path
      (register + GetPrimitiveAdapterByType + HydratePrimitive), mirroring the
      composite/primitive naming split used across the other GLVs.
    - GraphBinaryReader hydrates PrimitiveProviderDefinedType via the registry.
    - GremlinLang text translation emits PDT("name","value") for a
      PrimitiveProviderDefinedType and auto-dehydrates registered types — the
      client-side text path that was the Python gap.
    - ADAPTER-OVER-ATTRIBUTE precedence: a registered adapter takes priority 
over
      the [ProviderDefined] attribute on dehydration (matching the Java/Python 
fix
      in ef194e358f), applied in the text path.
    - Client wiring reuses the existing SetPdtRegistry / GremlinClient /
      DriverRemoteConnection path.
    
    No GraphSON g:PrimitivePdt read path added (consistent with the .NET 
driver's
    GraphBinary-based V4 response handling).
    
    Tests: 57 unit tests pass (serializer round-trip incl. opaque-value 
fidelity,
    registry hydration, gremlin-lang text emission, adapter-over-attribute
    precedence). Integration tests (raw, opaque value, in-collection,
    nested-in-composite, registered) pass against the test server: 6/6.
    
    tinkerpop-2gy.11
    
    Assisted-by: Kiro:claude-opus-4.8
---
 .../Gremlin.Net/Process/Traversal/GremlinLang.cs   |  15 +-
 .../Structure/IO/GraphBinary4/DataType.cs          |   3 +-
 .../Structure/IO/GraphBinary4/GraphBinaryReader.cs |  12 +-
 .../IO/GraphBinary4/TypeSerializerRegistry.cs      |   2 +
 .../GraphBinary4/Types/PrimitivePDTSerializer.cs   |  66 +++++++
 .../Gremlin.Net/Structure/IPrimitivePdtAdapter.cs  |  47 +++++
 .../Structure/PrimitiveProviderDefinedType.cs      |  65 +++++++
 .../Structure/ProviderDefinedTypeRegistry.cs       | 138 +++++++++++---
 .../Driver/DriverRemoteConnectionTests.cs          |  51 +++++-
 .../Driver/GremlinClientTests.cs                   |  72 ++++++++
 .../Process/Traversal/GremlinLangTests.cs          |  73 ++++++++
 .../PrimitiveProviderDefinedTypeTests.cs           | 204 +++++++++++++++++++++
 .../Structure/PrimitivePdtRegistryTests.cs         | 126 +++++++++++++
 13 files changed, 844 insertions(+), 30 deletions(-)

diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs 
b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
index 684a76c2f6..ce9bb1e1d5 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GremlinLang.cs
@@ -356,6 +356,11 @@ namespace Gremlin.Net.Process.Traversal
                 sb2.Append(']');
                 return $"PDT(\"{EscapeJava(pdt.Name)}\",{sb2})";
             }
+
+            if (arg is PrimitiveProviderDefinedType primitivePdt)
+            {
+                return 
$"PDT(\"{EscapeJava(primitivePdt.Name)}\",\"{EscapeJava(primitivePdt.Value)}\")";
+            }
             if (arg is IDictionary dict)
                 return AsString(dict);
 
@@ -377,9 +382,17 @@ namespace Gremlin.Net.Process.Traversal
 
             // Precedence: a registered adapter intentionally takes priority 
over the [ProviderDefined]
             // attribute so that explicit adapters can override 
attribute-derived dehydration behavior.
+            // Check primitive adapter first, then composite.
             if (PdtRegistry != null)
             {
-                var adapterInfo = PdtRegistry.GetAdapterByType(arg.GetType());
+                var primitiveInfo = 
PdtRegistry.GetPrimitiveAdapterByType(arg.GetType());
+                if (primitiveInfo != null)
+                {
+                    var (adapterTypeName, toStr) = primitiveInfo.Value;
+                    return ArgAsString(new 
PrimitiveProviderDefinedType(adapterTypeName, toStr(arg)));
+                }
+
+                var adapterInfo = 
PdtRegistry.GetCompositeAdapterByType(arg.GetType());
                 if (adapterInfo != null)
                 {
                     var (adapterTypeName, toFields) = adapterInfo.Value;
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/DataType.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/DataType.cs
index a8844bbb47..bc48d9edf4 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/DataType.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/DataType.cs
@@ -59,8 +59,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary4
         // public static readonly DataType Tree = new DataType(0x2B);
         public static readonly DataType Merge = new DataType(0x2E);
         public static readonly DataType CompositePDT = new DataType(0xF0);
-        // Not yet implemented
-        // public static readonly DataType PrimitivePDT = new DataType(0xF1);
+        public static readonly DataType PrimitivePDT = new DataType(0xF1);
         public static readonly DataType Char = new DataType(0x80);
         public static readonly DataType Duration = new DataType(0x81);
         public static readonly DataType Marker = new DataType(0xFD);
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/GraphBinaryReader.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/GraphBinaryReader.cs
index 97f0dd76fe..76d4b40999 100644
--- 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/GraphBinaryReader.cs
+++ 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/GraphBinaryReader.cs
@@ -99,12 +99,22 @@ namespace Gremlin.Net.Structure.IO.GraphBinary4
             {
                 if (_pdtRegistry != null)
                 {
-                    var hydrated = _pdtRegistry.Hydrate(pdt);
+                    var hydrated = _pdtRegistry.HydrateComposite(pdt);
                     if (hydrated is not ProviderDefinedType)
                         return hydrated;
                 }
                 return ProviderDefinedAttribute.HydrateIfRegistered(pdt);
             }
+            if (result is PrimitiveProviderDefinedType primitivePdt)
+            {
+                if (_pdtRegistry != null)
+                {
+                    var hydrated = _pdtRegistry.HydratePrimitive(primitivePdt);
+                    if (hydrated is not PrimitiveProviderDefinedType)
+                        return hydrated;
+                }
+                return primitivePdt;
+            }
             return result;
         }
     }
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/TypeSerializerRegistry.cs
 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/TypeSerializerRegistry.cs
index 8c48079668..e0f02fea61 100644
--- 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/TypeSerializerRegistry.cs
+++ 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/TypeSerializerRegistry.cs
@@ -66,6 +66,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary4
                 {typeof(TimeSpan), new DurationSerializer()},
                 {typeof(Marker), SingleTypeSerializers.MarkerSerializer},
                 {typeof(ProviderDefinedType), new CompositePDTSerializer()},
+                {typeof(PrimitiveProviderDefinedType), new 
PrimitivePDTSerializer()},
             };
 
         private readonly Dictionary<DataType, ITypeSerializer> 
_serializerByDataType =
@@ -100,6 +101,7 @@ namespace Gremlin.Net.Structure.IO.GraphBinary4
                 {DataType.Duration, new DurationSerializer()},
                 {DataType.Marker, SingleTypeSerializers.MarkerSerializer},
                 {DataType.CompositePDT, new CompositePDTSerializer()},
+                {DataType.PrimitivePDT, new PrimitivePDTSerializer()},
             };
 
         /// <summary>
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/PrimitivePDTSerializer.cs
 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/PrimitivePDTSerializer.cs
new file mode 100644
index 0000000000..9d84f8c191
--- /dev/null
+++ 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary4/Types/PrimitivePDTSerializer.cs
@@ -0,0 +1,66 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Gremlin.Net.Structure.IO.GraphBinary4.Types
+{
+    /// <summary>
+    /// A <see cref="PrimitiveProviderDefinedType"/> serializer for the 
PrimitivePDT data type.
+    /// Wire format: two fully-qualified Strings {name}{value}.
+    /// </summary>
+    public class PrimitivePDTSerializer : 
SimpleTypeSerializer<PrimitiveProviderDefinedType>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see 
cref="PrimitivePDTSerializer"/> class.
+        /// </summary>
+        public PrimitivePDTSerializer() : base(DataType.PrimitivePDT)
+        {
+        }
+
+        /// <inheritdoc />
+        protected override async Task 
WriteValueAsync(PrimitiveProviderDefinedType value, Stream stream,
+            GraphBinaryWriter writer, CancellationToken cancellationToken = 
default)
+        {
+            await writer.WriteAsync(value.Name, stream, 
cancellationToken).ConfigureAwait(false);
+            await writer.WriteAsync(value.Value, stream, 
cancellationToken).ConfigureAwait(false);
+        }
+
+        /// <inheritdoc />
+        protected override async Task<PrimitiveProviderDefinedType> 
ReadValueAsync(Stream stream,
+            GraphBinaryReader reader, CancellationToken cancellationToken = 
default)
+        {
+            var name = await reader.ReadAsync(stream, 
cancellationToken).ConfigureAwait(false) as string;
+            if (string.IsNullOrEmpty(name))
+                throw new IOException("PrimitivePDT name cannot be null or 
empty.");
+
+            var value = await reader.ReadAsync(stream, 
cancellationToken).ConfigureAwait(false) as string;
+            if (value == null)
+                throw new IOException("PrimitivePDT value cannot be null.");
+
+            return new PrimitiveProviderDefinedType(name!, value);
+        }
+    }
+}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IPrimitivePdtAdapter.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/IPrimitivePdtAdapter.cs
new file mode 100644
index 0000000000..78240a0e15
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IPrimitivePdtAdapter.cs
@@ -0,0 +1,47 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+namespace Gremlin.Net.Structure
+{
+    /// <summary>
+    /// Adapter for hydrating a <see cref="PrimitiveProviderDefinedType"/> 
into a strongly-typed object.
+    /// </summary>
+    /// <typeparam name="T">The target type to hydrate into.</typeparam>
+    public interface IPrimitivePdtAdapter<T>
+    {
+        /// <summary>
+        /// Gets the fully-qualified type name this adapter handles.
+        /// </summary>
+        string TypeName { get; }
+
+        /// <summary>
+        /// Creates a typed instance from the opaque string value.
+        /// </summary>
+        T FromString(string value);
+
+        /// <summary>
+        /// Converts a typed instance to its opaque string representation.
+        /// </summary>
+        string ToString(T obj);
+    }
+}
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/PrimitiveProviderDefinedType.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/PrimitiveProviderDefinedType.cs
new file mode 100644
index 0000000000..83f8932666
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/PrimitiveProviderDefinedType.cs
@@ -0,0 +1,65 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+
+namespace Gremlin.Net.Structure
+{
+    /// <summary>
+    /// Represents a primitive provider-defined type (PDT) with a name and an 
opaque string value.
+    /// </summary>
+    public class PrimitiveProviderDefinedType
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see 
cref="PrimitiveProviderDefinedType"/> class.
+        /// </summary>
+        /// <param name="name">The fully-qualified name of the 
provider-defined type.</param>
+        /// <param name="value">The opaque string value.</param>
+        public PrimitiveProviderDefinedType(string name, string value)
+        {
+            Name = name ?? throw new ArgumentNullException(nameof(name));
+            if (string.IsNullOrEmpty(name)) throw new ArgumentException("name 
cannot be empty", nameof(name));
+            Value = value ?? throw new ArgumentNullException(nameof(value));
+        }
+
+        /// <summary>
+        /// Gets the fully-qualified name of this primitive provider-defined 
type.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the opaque string value of this primitive provider-defined 
type.
+        /// </summary>
+        public string Value { get; }
+
+        /// <inheritdoc />
+        public override string ToString() => $"pdt[{Name}]{{{Value}}}";
+
+        /// <inheritdoc />
+        public override bool Equals(object? obj) =>
+            obj is PrimitiveProviderDefinedType other && Name == other.Name && 
Value == other.Value;
+
+        /// <inheritdoc />
+        public override int GetHashCode() => HashCode.Combine(Name, Value);
+    }
+}
diff --git 
a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs 
b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs
index 427a35ca44..46843d74b0 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/ProviderDefinedTypeRegistry.cs
@@ -30,27 +30,39 @@ using System.Reflection;
 namespace Gremlin.Net.Structure
 {
     /// <summary>
-    /// Registry for <see cref="IProviderDefinedTypeAdapter{T}"/> instances 
that hydrate
-    /// <see cref="ProviderDefinedType"/> values into strongly-typed objects.
+    /// Registry for <see cref="IProviderDefinedTypeAdapter{T}"/> and <see 
cref="IPrimitivePdtAdapter{T}"/>
+    /// instances that hydrate provider-defined types into strongly-typed 
objects.
     /// </summary>
     public class ProviderDefinedTypeRegistry
     {
-        private readonly Dictionary<string, object> _adaptersByName = new();
-        private readonly Dictionary<Type, (string typeName, object adapter)> 
_adaptersByType = new();
+        private readonly Dictionary<string, object> _compositeAdaptersByName = 
new();
+        private readonly Dictionary<Type, (string typeName, object adapter)> 
_compositeAdaptersByType = new();
+        private readonly Dictionary<string, object> _primitiveAdaptersByName = 
new();
+        private readonly Dictionary<Type, (string typeName, object adapter)> 
_primitiveAdaptersByType = new();
 
         /// <summary>
-        /// Registers an adapter for a specific provider-defined type name.
+        /// Registers a composite adapter for a specific provider-defined type 
name.
         /// </summary>
         public void Register<T>(IProviderDefinedTypeAdapter<T> adapter)
         {
-            _adaptersByName[adapter.TypeName] = adapter;
-            _adaptersByType[typeof(T)] = (adapter.TypeName, adapter);
+            _compositeAdaptersByName[adapter.TypeName] = adapter;
+            _compositeAdaptersByType[typeof(T)] = (adapter.TypeName, adapter);
+        }
+
+        /// <summary>
+        /// Registers a primitive adapter for a specific provider-defined type 
name.
+        /// </summary>
+        public void RegisterPrimitive<T>(IPrimitivePdtAdapter<T> adapter)
+        {
+            _primitiveAdaptersByName[adapter.TypeName] = adapter;
+            _primitiveAdaptersByType[typeof(T)] = (adapter.TypeName, adapter);
         }
 
         /// <summary>
         /// Creates a registry populated by scanning loaded assemblies for:
         /// <list type="bullet">
-        /// <item>Types implementing <see 
cref="IProviderDefinedTypeAdapter{T}"/> (adapter-based hydration)</item>
+        /// <item>Types implementing <see 
cref="IProviderDefinedTypeAdapter{T}"/> (composite adapter-based 
hydration)</item>
+        /// <item>Types implementing <see cref="IPrimitivePdtAdapter{T}"/> 
(primitive adapter-based hydration)</item>
         /// <item>Types annotated with <see cref="ProviderDefinedAttribute"/> 
(annotation-based round-trip)</item>
         /// </list>
         /// </summary>
@@ -64,18 +76,38 @@ namespace Gremlin.Net.Structure
                 {
                     foreach (var type in assembly.GetTypes())
                     {
-                        // Register adapter implementations
-                        var adapterInterface = type.GetInterfaces()
+                        // Register composite adapter implementations
+                        var compositeInterface = type.GetInterfaces()
                             .FirstOrDefault(i => i.IsGenericType &&
                                 i.GetGenericTypeDefinition() == 
typeof(IProviderDefinedTypeAdapter<>));
-                        if (adapterInterface != null && !type.IsAbstract && 
!type.IsInterface)
+                        if (compositeInterface != null && !type.IsAbstract && 
!type.IsInterface)
                         {
                             try
                             {
                                 var adapter = Activator.CreateInstance(type);
                                 var registerMethod = 
typeof(ProviderDefinedTypeRegistry)
                                     .GetMethod(nameof(Register))!
-                                    
.MakeGenericMethod(adapterInterface.GetGenericArguments()[0]);
+                                    
.MakeGenericMethod(compositeInterface.GetGenericArguments()[0]);
+                                registerMethod.Invoke(registry, new[] { 
adapter });
+                            }
+                            catch
+                            {
+                                // skip types that can't be instantiated
+                            }
+                        }
+
+                        // Register primitive adapter implementations
+                        var primitiveInterface = type.GetInterfaces()
+                            .FirstOrDefault(i => i.IsGenericType &&
+                                i.GetGenericTypeDefinition() == 
typeof(IPrimitivePdtAdapter<>));
+                        if (primitiveInterface != null && !type.IsAbstract && 
!type.IsInterface)
+                        {
+                            try
+                            {
+                                var adapter = Activator.CreateInstance(type);
+                                var registerMethod = 
typeof(ProviderDefinedTypeRegistry)
+                                    .GetMethod(nameof(RegisterPrimitive))!
+                                    
.MakeGenericMethod(primitiveInterface.GetGenericArguments()[0]);
                                 registerMethod.Invoke(registry, new[] { 
adapter });
                             }
                             catch
@@ -103,11 +135,11 @@ namespace Gremlin.Net.Structure
         }
 
         /// <summary>
-        /// Returns the type name and ToFields method for the given CLR type, 
or null if not registered.
+        /// Returns the type name and ToFields method for the given CLR type, 
or null if not registered as composite.
         /// </summary>
-        internal (string typeName, Func<object, IReadOnlyDictionary<string, 
object?>>)? GetAdapterByType(Type type)
+        internal (string typeName, Func<object, IReadOnlyDictionary<string, 
object?>>)? GetCompositeAdapterByType(Type type)
         {
-            if (!_adaptersByType.TryGetValue(type, out var entry))
+            if (!_compositeAdaptersByType.TryGetValue(type, out var entry))
                 return null;
             var method = entry.adapter.GetType().GetMethod("ToFields");
             if (method == null) return null;
@@ -115,25 +147,50 @@ namespace Gremlin.Net.Structure
         }
 
         /// <summary>
-        /// Hydrates a <see cref="ProviderDefinedType"/> into a typed object 
using a registered adapter.
+        /// Returns the type name and ToString method for the given CLR type, 
or null if not registered as primitive.
+        /// </summary>
+        internal (string typeName, Func<object, string>)? 
GetPrimitiveAdapterByType(Type type)
+        {
+            if (!_primitiveAdaptersByType.TryGetValue(type, out var entry))
+                return null;
+            var method = entry.adapter.GetType().GetMethod("ToString", new[] { 
type });
+            if (method == null) return null;
+            return (entry.typeName, obj => 
(string)method.Invoke(entry.adapter, new[] { obj })!);
+        }
+
+        /// <summary>
+        /// Returns the type name and ToFields method for the given CLR type 
(composite),
+        /// or type name and ToString method (primitive). Checks primitive 
first, then composite.
+        /// Returns null if not registered.
+        /// </summary>
+        internal (string typeName, Func<object, IReadOnlyDictionary<string, 
object?>>)? GetAdapterByType(Type type)
+        {
+            // Check composite adapters (backward compatibility with existing 
callers)
+            return GetCompositeAdapterByType(type);
+        }
+
+        /// <summary>
+        /// Hydrates a <see cref="ProviderDefinedType"/> into a typed object 
using a registered composite adapter.
         /// Returns the original PDT if no adapter is registered or if 
hydration fails.
         /// </summary>
-        public object Hydrate(ProviderDefinedType pdt)
+        public object HydrateComposite(ProviderDefinedType pdt)
         {
-            if (!_adaptersByName.TryGetValue(pdt.Name, out var adapterObj))
+            if (!_compositeAdaptersByName.TryGetValue(pdt.Name, out var 
adapterObj))
             {
                 // No adapter for outer — still recurse into nested PDT fields
                 Dictionary<string, object?>? resolved = null;
                 foreach (var (key, value) in pdt.Fields)
                 {
+                    object? hydrated = value;
                     if (value is ProviderDefinedType nested)
+                        hydrated = HydrateComposite(nested);
+                    else if (value is PrimitiveProviderDefinedType nestedPrim)
+                        hydrated = HydratePrimitive(nestedPrim);
+
+                    if (!ReferenceEquals(hydrated, value))
                     {
-                        var hydrated = Hydrate(nested);
-                        if (!ReferenceEquals(hydrated, nested))
-                        {
-                            resolved ??= new Dictionary<string, 
object?>(pdt.Fields);
-                            resolved[key] = hydrated;
-                        }
+                        resolved ??= new Dictionary<string, 
object?>(pdt.Fields);
+                        resolved[key] = hydrated;
                     }
                 }
                 return resolved != null ? new ProviderDefinedType(pdt.Name, 
resolved) : pdt;
@@ -143,7 +200,12 @@ namespace Gremlin.Net.Structure
                 var hydratedFields = new Dictionary<string, object?>();
                 foreach (var (key, value) in pdt.Fields)
                 {
-                    hydratedFields[key] = value is ProviderDefinedType nested 
? Hydrate(nested) : value;
+                    if (value is ProviderDefinedType nested)
+                        hydratedFields[key] = HydrateComposite(nested);
+                    else if (value is PrimitiveProviderDefinedType nestedPrim)
+                        hydratedFields[key] = HydratePrimitive(nestedPrim);
+                    else
+                        hydratedFields[key] = value;
                 }
 
                 var readOnlyFields = new ReadOnlyDictionary<string, 
object?>(hydratedFields);
@@ -155,5 +217,31 @@ namespace Gremlin.Net.Structure
                 return pdt;
             }
         }
+
+        /// <summary>
+        /// Hydrates a <see cref="PrimitiveProviderDefinedType"/> into a typed 
object using a registered primitive adapter.
+        /// Returns the original primitive PDT if no adapter is registered or 
if hydration fails.
+        /// </summary>
+        public object HydratePrimitive(PrimitiveProviderDefinedType pdt)
+        {
+            if (!_primitiveAdaptersByName.TryGetValue(pdt.Name, out var 
adapterObj))
+                return pdt;
+            try
+            {
+                var method = adapterObj.GetType().GetMethod("FromString");
+                return method!.Invoke(adapterObj, new object[] { pdt.Value })!;
+            }
+            catch (Exception)
+            {
+                return pdt;
+            }
+        }
+
+        /// <summary>
+        /// Hydrates a <see cref="ProviderDefinedType"/> into a typed object 
using a registered composite adapter.
+        /// Returns the original PDT if no adapter is registered or if 
hydration fails.
+        /// </summary>
+        [Obsolete("Use HydrateComposite instead.")]
+        public object Hydrate(ProviderDefinedType pdt) => 
HydrateComposite(pdt);
     }
 }
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
index 3aaedb218e..e28742bef2 100644
--- 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
+++ 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/DriverRemoteConnectionTests.cs
@@ -192,4 +192,53 @@ public class DriverRemoteConnectionTests
     }
 
     #endregion
-}
\ No newline at end of file
+
+    [Fact]
+    public void ShouldRoundTripPrimitivePdtViaTraversalApi()
+    {
+        var gremlinServer = new GremlinServer(TestHost, TestPort);
+        using var gremlinClient = new GremlinClient(gremlinServer);
+        using var connection = new DriverRemoteConnection(gremlinClient, 
"gmodern");
+        var g = AnonymousTraversalSource.Traversal().With(connection);
+
+        var pdt = new PrimitiveProviderDefinedType("TestToken", "abc123");
+
+        var results = g.Inject<object>(pdt).ToList();
+
+        Assert.Single(results);
+        var result = Assert.IsType<PrimitiveProviderDefinedType>(results[0]);
+        Assert.Equal("TestToken", result.Name);
+        Assert.Equal("abc123", result.Value);
+    }
+
+    [Fact]
+    public void ShouldRoundTripPrimitiveTypedObjectViaRegistry()
+    {
+        var registry = new ProviderDefinedTypeRegistry();
+        registry.RegisterPrimitive(new TestUint32Adapter());
+
+        var gremlinServer = new GremlinServer(TestHost, TestPort);
+        using var gremlinClient = new GremlinClient(gremlinServer, 
pdtRegistry: registry);
+        using var connection = new DriverRemoteConnection(gremlinClient, 
"gmodern", pdtRegistry: registry);
+        var g = AnonymousTraversalSource.Traversal().With(connection);
+
+        var val = 42u;
+
+        var results = g.Inject<object>(val).ToList();
+
+        Assert.Single(results);
+        Assert.IsType<uint>(results[0]);
+        Assert.Equal(42u, (uint)results[0]);
+    }
+
+    #region Test helpers (primitive)
+
+    private class TestUint32Adapter : IPrimitivePdtAdapter<uint>
+    {
+        public string TypeName => "TestToken";
+        public uint FromString(string value) => uint.Parse(value);
+        public string ToString(uint obj) => obj.ToString();
+    }
+
+    #endregion
+}
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs
index a5fdac7c8e..91507a517d 100644
--- 
a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs
+++ 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Driver/GremlinClientTests.cs
@@ -272,5 +272,77 @@ namespace Gremlin.Net.IntegrationTest.Driver
             Assert.Equal(3, p2.Fields["x"]);
             Assert.Equal(4, p2.Fields["y"]);
         }
+
+        [Fact]
+        public async Task ShouldRoundTripSimplePrimitivePdt()
+        {
+            var gremlinServer = new GremlinServer(TestHost, TestPort);
+            using var gremlinClient = new GremlinClient(gremlinServer);
+
+            var response = await gremlinClient.SubmitAsync<object>(
+                "g.inject(PDT(\"Uint32\", \"42\"))");
+            var results = await response.ToListAsync();
+
+            Assert.Single(results);
+            var pdt = Assert.IsType<PrimitiveProviderDefinedType>(results[0]);
+            Assert.Equal("Uint32", pdt.Name);
+            Assert.Equal("42", pdt.Value);
+        }
+
+        [Fact]
+        public async Task ShouldRoundTripPrimitivePdtWithOpaqueValue()
+        {
+            var gremlinServer = new GremlinServer(TestHost, TestPort);
+            using var gremlinClient = new GremlinClient(gremlinServer);
+
+            var response = await gremlinClient.SubmitAsync<object>(
+                "g.inject(PDT(\"Token\", \"007-abc\"))");
+            var results = await response.ToListAsync();
+
+            Assert.Single(results);
+            var pdt = Assert.IsType<PrimitiveProviderDefinedType>(results[0]);
+            Assert.Equal("Token", pdt.Name);
+            Assert.Equal("007-abc", pdt.Value);
+        }
+
+        [Fact]
+        public async Task ShouldHandlePrimitivePdtInCollection()
+        {
+            var gremlinServer = new GremlinServer(TestHost, TestPort);
+            using var gremlinClient = new GremlinClient(gremlinServer);
+
+            var response = await gremlinClient.SubmitAsync<object>(
+                "g.inject([PDT(\"Uint32\", \"1\"), PDT(\"Uint32\", \"2\")])");
+            var results = await response.ToListAsync();
+
+            Assert.Single(results);
+            var list = Assert.IsType<List<object>>(results[0]);
+            Assert.Equal(2, list.Count);
+
+            var p1 = Assert.IsType<PrimitiveProviderDefinedType>(list[0]);
+            Assert.Equal("1", p1.Value);
+
+            var p2 = Assert.IsType<PrimitiveProviderDefinedType>(list[1]);
+            Assert.Equal("2", p2.Value);
+        }
+
+        [Fact]
+        public async Task ShouldRoundTripPrimitivePdtNestedInComposite()
+        {
+            var gremlinServer = new GremlinServer(TestHost, TestPort);
+            using var gremlinClient = new GremlinClient(gremlinServer);
+
+            var response = await gremlinClient.SubmitAsync<object>(
+                "g.inject(PDT(\"Measurement\", [\"unit\":\"kg\", 
\"value\":PDT(\"Uint32\", \"100\")]))");
+            var results = await response.ToListAsync();
+
+            Assert.Single(results);
+            var pdt = Assert.IsType<ProviderDefinedType>(results[0]);
+            Assert.Equal("Measurement", pdt.Name);
+            Assert.Equal("kg", pdt.Fields["unit"]);
+            var inner = 
Assert.IsType<PrimitiveProviderDefinedType>(pdt.Fields["value"]);
+            Assert.Equal("Uint32", inner.Name);
+            Assert.Equal("100", inner.Value);
+        }
     }
 }
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
 
b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
index daca1acc07..bae2258f4a 100644
--- 
a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
+++ 
b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs
@@ -1231,5 +1231,78 @@ namespace Gremlin.Net.UnitTest.Process.Traversal
         {
             public string Tag { get; set; } = "";
         }
+
+        [Fact]
+        public void g_Inject_PrimitivePDT_basic()
+        {
+            var pdt = new PrimitiveProviderDefinedType("Uint32", "42");
+            var result = _g.Inject((object)pdt).GremlinLang.GetGremlin();
+            Assert.Equal("g.inject(PDT(\"Uint32\",\"42\"))", result);
+        }
+
+        [Fact]
+        public void g_Inject_PrimitivePDT_special_chars_in_value()
+        {
+            var pdt = new PrimitiveProviderDefinedType("Token", 
"hello\"world");
+            var result = _g.Inject((object)pdt).GremlinLang.GetGremlin();
+            Assert.Equal("g.inject(PDT(\"Token\",\"hello\\\"world\"))", 
result);
+        }
+
+        [Fact]
+        public void g_Inject_PrimitivePDT_leading_zeros()
+        {
+            var pdt = new PrimitiveProviderDefinedType("Padded", "007");
+            var result = _g.Inject((object)pdt).GremlinLang.GetGremlin();
+            Assert.Equal("g.inject(PDT(\"Padded\",\"007\"))", result);
+        }
+
+        [Fact]
+        public void 
g_Inject_PrimitivePDT_auto_dehydration_via_primitive_adapter()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new TestUint32Adapter());
+
+            var g = new GraphTraversalSource();
+            g.GremlinLang.PdtRegistry = registry;
+
+            var result = g.Inject((object)99u).GremlinLang.GetGremlin();
+            Assert.Equal("g.inject(PDT(\"test:Uint32\",\"99\"))", result);
+        }
+
+        [Fact]
+        public void 
g_Inject_PrimitivePDT_adapter_takes_precedence_over_attribute()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new PrimitiveAdapterForAnnotatedType());
+
+            var g = new GraphTraversalSource();
+            g.GremlinLang.PdtRegistry = registry;
+
+            var obj = new AnnotatedButPrimitiveAdapted { Data = "hello" };
+            var result = g.Inject((object)obj).GremlinLang.GetGremlin();
+
+            // The primitive adapter should win over [ProviderDefined] 
attribute
+            Assert.Equal("g.inject(PDT(\"prim:Adapted\",\"hello\"))", result);
+        }
+
+        [ProviderDefined(Name = "attr.Annotated")]
+        private class AnnotatedButPrimitiveAdapted
+        {
+            public string Data { get; set; } = "";
+        }
+
+        private class PrimitiveAdapterForAnnotatedType : 
IPrimitivePdtAdapter<AnnotatedButPrimitiveAdapted>
+        {
+            public string TypeName => "prim:Adapted";
+            public AnnotatedButPrimitiveAdapted FromString(string value) => 
new() { Data = value };
+            public string ToString(AnnotatedButPrimitiveAdapted obj) => 
obj.Data;
+        }
+
+        private class TestUint32Adapter : IPrimitivePdtAdapter<uint>
+        {
+            public string TypeName => "test:Uint32";
+            public uint FromString(string value) => uint.Parse(value);
+            public string ToString(uint obj) => obj.ToString();
+        }
     }
 }
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/PrimitiveProviderDefinedTypeTests.cs
 
b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/PrimitiveProviderDefinedTypeTests.cs
new file mode 100644
index 0000000000..e96d03e451
--- /dev/null
+++ 
b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary4/PrimitiveProviderDefinedTypeTests.cs
@@ -0,0 +1,204 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Gremlin.Net.Structure;
+using Gremlin.Net.Structure.IO.GraphBinary4;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Structure.IO.GraphBinary4
+{
+    public class PrimitiveProviderDefinedTypeTests
+    {
+        private static readonly GraphBinaryWriter Writer = new();
+        private static readonly GraphBinaryReader Reader = new();
+
+        [Fact]
+        public async Task TestRoundTripBasic()
+        {
+            var expected = new 
PrimitiveProviderDefinedType("com.example.Uint32", "42");
+
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(expected, stream);
+            stream.Position = 0;
+            var actual = await Reader.ReadAsync(stream) as 
PrimitiveProviderDefinedType;
+
+            Assert.NotNull(actual);
+            Assert.Equal(expected.Name, actual!.Name);
+            Assert.Equal(expected.Value, actual.Value);
+        }
+
+        [Fact]
+        public async Task TestRoundTripWithLeadingZeros()
+        {
+            var expected = new 
PrimitiveProviderDefinedType("com.example.Padded", "007");
+
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(expected, stream);
+            stream.Position = 0;
+            var actual = await Reader.ReadAsync(stream) as 
PrimitiveProviderDefinedType;
+
+            Assert.NotNull(actual);
+            Assert.Equal("007", actual!.Value);
+        }
+
+        [Fact]
+        public async Task TestRoundTripWithLargeNumber()
+        {
+            var expected = new 
PrimitiveProviderDefinedType("com.example.BigNum",
+                "99999999999999999999999999999999");
+
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(expected, stream);
+            stream.Position = 0;
+            var actual = await Reader.ReadAsync(stream) as 
PrimitiveProviderDefinedType;
+
+            Assert.NotNull(actual);
+            Assert.Equal("99999999999999999999999999999999", actual!.Value);
+        }
+
+        [Fact]
+        public async Task TestRoundTripNonNumericValue()
+        {
+            var expected = new 
PrimitiveProviderDefinedType("com.example.Token", "abc-def-123");
+
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(expected, stream);
+            stream.Position = 0;
+            var actual = await Reader.ReadAsync(stream) as 
PrimitiveProviderDefinedType;
+
+            Assert.NotNull(actual);
+            Assert.Equal("abc-def-123", actual!.Value);
+        }
+
+        [Fact]
+        public async Task TestRoundTripEmptyValue()
+        {
+            var expected = new 
PrimitiveProviderDefinedType("com.example.Empty", "");
+
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(expected, stream);
+            stream.Position = 0;
+            var actual = await Reader.ReadAsync(stream) as 
PrimitiveProviderDefinedType;
+
+            Assert.NotNull(actual);
+            Assert.Equal("", actual!.Value);
+        }
+
+        [Fact]
+        public async Task TestDataTypeCode()
+        {
+            var pdt = new PrimitiveProviderDefinedType("com.example.Test", 
"val");
+
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(pdt, stream);
+
+            Assert.Equal(0xF1, stream.ToArray()[0]);
+        }
+
+        [Fact]
+        public void TestConstructorThrowsOnNullName()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+                new PrimitiveProviderDefinedType(null!, "val"));
+        }
+
+        [Fact]
+        public void TestConstructorThrowsOnEmptyName()
+        {
+            Assert.Throws<ArgumentException>(() =>
+                new PrimitiveProviderDefinedType("", "val"));
+        }
+
+        [Fact]
+        public void TestConstructorThrowsOnNullValue()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+                new PrimitiveProviderDefinedType("com.example.T", null!));
+        }
+
+        [Fact]
+        public void TestEquality()
+        {
+            var a = new PrimitiveProviderDefinedType("com.example.T", "42");
+            var b = new PrimitiveProviderDefinedType("com.example.T", "42");
+            Assert.Equal(a, b);
+            Assert.Equal(a.GetHashCode(), b.GetHashCode());
+        }
+
+        [Fact]
+        public void TestInequality()
+        {
+            var a = new PrimitiveProviderDefinedType("com.example.A", "1");
+            var b = new PrimitiveProviderDefinedType("com.example.B", "1");
+            Assert.NotEqual(a, b);
+        }
+
+        [Fact]
+        public void TestToString()
+        {
+            var pdt = new PrimitiveProviderDefinedType("com.example.T", "42");
+            Assert.Contains("com.example.T", pdt.ToString());
+            Assert.Contains("42", pdt.ToString());
+        }
+
+        [Fact]
+        public async Task TestHydrationWithRegistry()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new TestUint32Adapter());
+            var reader = new GraphBinaryReader(pdtRegistry: registry);
+
+            var pdt = new PrimitiveProviderDefinedType("test:Uint32", "123");
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(pdt, stream);
+            stream.Position = 0;
+            var result = await reader.ReadAsync(stream);
+
+            Assert.IsType<uint>(result);
+            Assert.Equal(123u, (uint)result);
+        }
+
+        [Fact]
+        public async Task TestNoHydrationWithoutRegistry()
+        {
+            var pdt = new PrimitiveProviderDefinedType("test:Uint32", "456");
+            using var stream = new MemoryStream();
+            await Writer.WriteAsync(pdt, stream);
+            stream.Position = 0;
+            var result = await Reader.ReadAsync(stream);
+
+            Assert.IsType<PrimitiveProviderDefinedType>(result);
+            Assert.Equal("456", ((PrimitiveProviderDefinedType)result).Value);
+        }
+
+        private class TestUint32Adapter : IPrimitivePdtAdapter<uint>
+        {
+            public string TypeName => "test:Uint32";
+            public uint FromString(string value) => uint.Parse(value);
+            public string ToString(uint obj) => obj.ToString();
+        }
+    }
+}
diff --git 
a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/PrimitivePdtRegistryTests.cs
 
b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/PrimitivePdtRegistryTests.cs
new file mode 100644
index 0000000000..ad33728399
--- /dev/null
+++ 
b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/PrimitivePdtRegistryTests.cs
@@ -0,0 +1,126 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Gremlin.Net.Structure;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Structure
+{
+    public class PrimitivePdtRegistryTests
+    {
+        [Fact]
+        public void ShouldHydratePrimitiveWhenAdapterRegistered()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new Uint32Adapter());
+            var pdt = new PrimitiveProviderDefinedType("test:Uint32", "42");
+
+            var result = registry.HydratePrimitive(pdt);
+
+            Assert.IsType<uint>(result);
+            Assert.Equal(42u, (uint)result);
+        }
+
+        [Fact]
+        public void ShouldReturnRawPrimitivePdtWhenNoAdapterRegistered()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            var pdt = new PrimitiveProviderDefinedType("unknown:Type", 
"hello");
+
+            var result = registry.HydratePrimitive(pdt);
+
+            Assert.Same(pdt, result);
+        }
+
+        [Fact]
+        public void ShouldReturnRawPrimitivePdtWhenAdapterThrows()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new ThrowingPrimitiveAdapter());
+            var pdt = new PrimitiveProviderDefinedType("bad:Type", "oops");
+
+            var result = registry.HydratePrimitive(pdt);
+
+            Assert.Same(pdt, result);
+        }
+
+        [Fact]
+        public void ShouldHydratePrimitiveNestedInComposite()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new Uint32Adapter());
+            var inner = new PrimitiveProviderDefinedType("test:Uint32", "99");
+            var outer = new ProviderDefinedType("unregistered:Wrapper",
+                new Dictionary<string, object?> { ["val"] = inner, ["label"] = 
"test" });
+
+            var result = registry.HydrateComposite(outer);
+
+            var rawOuter = Assert.IsType<ProviderDefinedType>(result);
+            Assert.Equal(99u, (uint)rawOuter.Fields["val"]!);
+            Assert.Equal("test", rawOuter.Fields["label"]);
+        }
+
+        [Fact]
+        public void ShouldGetPrimitiveAdapterByType()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+            registry.RegisterPrimitive(new Uint32Adapter());
+
+            var info = registry.GetPrimitiveAdapterByType(typeof(uint));
+
+            Assert.NotNull(info);
+            Assert.Equal("test:Uint32", info!.Value.typeName);
+            Assert.Equal("123", info.Value.Item2(123u));
+        }
+
+        [Fact]
+        public void ShouldReturnNullForUnregisteredPrimitiveType()
+        {
+            var registry = new ProviderDefinedTypeRegistry();
+
+            var info = registry.GetPrimitiveAdapterByType(typeof(uint));
+
+            Assert.Null(info);
+        }
+
+        #region Test helpers
+
+        private class Uint32Adapter : IPrimitivePdtAdapter<uint>
+        {
+            public string TypeName => "test:Uint32";
+            public uint FromString(string value) => uint.Parse(value);
+            public string ToString(uint obj) => obj.ToString();
+        }
+
+        private class ThrowingPrimitiveAdapter : IPrimitivePdtAdapter<object>
+        {
+            public string TypeName => "bad:Type";
+            public object FromString(string value) => throw new 
InvalidOperationException("intentional");
+            public string ToString(object obj) => throw new 
InvalidOperationException("intentional");
+        }
+
+        #endregion
+    }
+}

Reply via email to