1+ module FSharp.Data.Core.Tests.XmlInference
2+
3+ open FsUnit
4+ open NUnit.Framework
5+ open System
6+ open System.Xml .Linq
7+ open FSharp.Data
8+ open FSharp.Data .Runtime
9+ open FSharp.Data .Runtime .StructuralTypes
10+ open FSharp.Data .Runtime .StructuralInference
11+ open ProviderImplementation
12+
13+ // Test infrastructure similar to InferenceTests.fs
14+ let internal culture = TextRuntime.GetCulture " "
15+ let internal inferenceMode = InferenceMode'.ValuesOnly
16+ let internal unitsOfMeasureProvider =
17+ { new StructuralInference.IUnitsOfMeasureProvider with
18+ member x.SI ( _ ) : System.Type = null
19+ member x.Product ( _ , _ ) = failwith " Not implemented yet"
20+ member x.Inverse ( _ ) = failwith " Not implemented yet" }
21+ let internal allowEmptyValues = true
22+
23+ // Helper function to create XElement from string
24+ let createElement xmlString =
25+ XElement.Parse( xmlString)
26+
27+ // Helper function to create XElement array
28+ let createElements xmlStrings =
29+ xmlStrings |> Array.map createElement
30+
31+ [<Test>]
32+ let ``getInferedTypeFromValue handles simple string value`` () =
33+ let element = createElement """ <name>John</name>"""
34+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
35+
36+ match result with
37+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< string>
38+ | _ -> failwith " Expected primitive string type"
39+
40+ [<Test>]
41+ let ``getInferedTypeFromValue handles numeric value`` () =
42+ let element = createElement """ <age>30</age>"""
43+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
44+
45+ match result with
46+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< int>
47+ | _ -> failwith " Expected primitive int type"
48+
49+ [<Test>]
50+ let ``getInferedTypeFromValue handles boolean value`` () =
51+ let element = createElement """ <active>true</active>"""
52+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
53+
54+ match result with
55+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< bool>
56+ | _ -> failwith " Expected primitive bool type"
57+
58+ [<Test>]
59+ let ``getInferedTypeFromValue handles decimal value`` () =
60+ let element = createElement """ <price>19.99</price>"""
61+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
62+
63+ match result with
64+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< decimal>
65+ | _ -> failwith " Expected primitive decimal type"
66+
67+ [<Test>]
68+ let ``getInferedTypeFromValue handles embedded JSON object`` () =
69+ let element = createElement """ <data>{"name": "John", "age": 30}</data>"""
70+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
71+
72+ match result with
73+ | InferedType.Json(_, _) -> () // Success - embedded JSON detected
74+ | _ -> failwith " Expected JSON type for embedded JSON content"
75+
76+ [<Test>]
77+ let ``getInferedTypeFromValue handles embedded JSON array`` () =
78+ let element = createElement """ <items>[1, 2, 3]</items>"""
79+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
80+
81+ match result with
82+ | InferedType.Json(_, _) -> () // Success - embedded JSON detected
83+ | _ -> failwith " Expected JSON type for embedded JSON array"
84+
85+ [<Test>]
86+ let ``getInferedTypeFromValue with NoInference mode skips JSON parsing`` () =
87+ let element = createElement """ <data>{"name": "John"}</data>"""
88+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider InferenceMode'.NoInference culture element
89+
90+ match result with
91+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< string>
92+ | _ -> failwith " Expected string type with NoInference mode"
93+
94+ [<Test>]
95+ let ``getInferedTypeFromValue handles malformed JSON as string`` () =
96+ let element = createElement """ <data>{"name": invalid}</data>"""
97+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
98+
99+ match result with
100+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< string>
101+ | _ -> failwith " Expected string type for malformed JSON"
102+
103+ [<Test>]
104+ let ``getInferedTypeFromValue handles empty element`` () =
105+ let element = createElement """ <empty></empty>"""
106+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
107+
108+ // For empty elements, the inference returns Null type
109+ match result with
110+ | InferedType.Null -> () // Success - empty value gives Null type
111+ | _ -> failwithf " Expected Null type for empty value, got %A " result
112+
113+ [<Test>]
114+ let ``inferLocalType handles simple element with content`` () =
115+ let element = createElement """ <name>John</name>"""
116+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
117+
118+ match result with
119+ | InferedType.Record( Some name, properties, false ) ->
120+ name |> should equal " name"
121+ properties.Length |> should equal 1
122+ properties.[ 0 ]. Name |> should equal " " // Body content
123+ | _ -> failwith " Expected record type with body content"
124+
125+ [<Test>]
126+ let ``inferLocalType handles element with attributes`` () =
127+ let element = createElement """ <person name="John" age="30">Developer</person>"""
128+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
129+
130+ match result with
131+ | InferedType.Record( Some name, properties, false ) ->
132+ name |> should equal " person"
133+ properties.Length |> should equal 3 // body content + 2 attributes
134+ properties |> List.exists ( fun p -> p.Name = " name" ) |> should equal true
135+ properties |> List.exists ( fun p -> p.Name = " age" ) |> should equal true
136+ properties |> List.exists ( fun p -> p.Name = " " ) |> should equal true // body
137+ | _ -> failwith " Expected record type with attributes and body"
138+
139+ [<Test>]
140+ let ``inferLocalType handles element with child elements`` () =
141+ let element = createElement """ <person><name>John</name><age>30</age></person>"""
142+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
143+
144+ match result with
145+ | InferedType.Record( Some name, properties, false ) ->
146+ name |> should equal " person"
147+ properties.Length |> should equal 1 // Collection of children
148+ properties.[ 0 ]. Name |> should equal " " // Body content (collection)
149+ match properties.[ 0 ]. Type with
150+ | InferedType.Collection(_, _) -> () // Success - collection of children
151+ | _ -> failwith " Expected collection type for child elements"
152+ | _ -> failwith " Expected record type with child collection"
153+
154+ [<Test>]
155+ let ``inferLocalType handles empty element`` () =
156+ let element = createElement """ <empty />"""
157+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
158+
159+ match result with
160+ | InferedType.Record( Some name, properties, false ) ->
161+ name |> should equal " empty"
162+ properties.Length |> should equal 0 // No content or attributes
163+ | _ -> failwith " Expected empty record type"
164+
165+ [<Test>]
166+ let ``inferLocalType handles element with only attributes`` () =
167+ let element = createElement """ <config debug="true" timeout="30" />"""
168+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
169+
170+ match result with
171+ | InferedType.Record( Some name, properties, false ) ->
172+ name |> should equal " config"
173+ properties.Length |> should equal 2 // 2 attributes, no body
174+ properties |> List.exists ( fun p -> p.Name = " debug" ) |> should equal true
175+ properties |> List.exists ( fun p -> p.Name = " timeout" ) |> should equal true
176+ | _ -> failwith " Expected record type with attributes only"
177+
178+ [<Test>]
179+ let ``inferLocalType handles nested structure`` () =
180+ let element = createElement """ <root><item id="1"><value>test</value></item><item id="2"><value>test2</value></item></root>"""
181+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
182+
183+ match result with
184+ | InferedType.Record( Some name, properties, false ) ->
185+ name |> should equal " root"
186+ properties.Length |> should equal 1
187+ match properties.[ 0 ]. Type with
188+ | InferedType.Collection(_, _) -> () // Collection of items
189+ | _ -> failwith " Expected collection of items"
190+ | _ -> failwith " Expected root record with item collection"
191+
192+ [<Test>]
193+ let ``inferGlobalType handles single element`` () =
194+ let doc = XDocument.Parse( """ <root><person name="John">Developer</person></root>""" )
195+ let elements = [| doc.Root |]
196+ let result = XmlInference.inferGlobalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues elements
197+
198+ result.Length |> should equal 1
199+ match result.[ 0 ] with
200+ | InferedType.Record( Some name, _, false ) ->
201+ name |> should equal " root"
202+ | _ -> failwith " Expected root record type"
203+
204+ [<Test>]
205+ let ``inferGlobalType handles multiple elements of same type`` () =
206+ let xml = """ <doc><person name="John" age="30" /><person name="Jane" city="NYC" /></doc>"""
207+ let doc = XDocument.Parse( xml)
208+ let elements = [| doc.Root |]
209+ let result = XmlInference.inferGlobalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues elements
210+
211+ result.Length |> should equal 1
212+
213+ [<Test>]
214+ let ``inferType with globalInference = true uses global inference`` () =
215+ let doc = XDocument.Parse( """ <root><person name="John">Developer</person></root>""" )
216+ let elements = [| doc.Root |]
217+ let result = XmlInference.inferType unitsOfMeasureProvider inferenceMode culture allowEmptyValues true elements
218+
219+ result.Length |> should equal 1
220+ match result.[ 0 ] with
221+ | InferedType.Record( Some name, _, false ) ->
222+ name |> should equal " root"
223+ | _ -> failwith " Expected root record type from global inference"
224+
225+ [<Test>]
226+ let ``inferType with globalInference = false uses local inference`` () =
227+ let elements = [| createElement """ <person name="John">Developer</person>""" |]
228+ let result = XmlInference.inferType unitsOfMeasureProvider inferenceMode culture allowEmptyValues false elements
229+
230+ result.Length |> should equal 1
231+ match result.[ 0 ] with
232+ | InferedType.Record( Some name, _, false ) ->
233+ name |> should equal " person"
234+ | _ -> failwith " Expected person record type from local inference"
235+
236+ [<Test>]
237+ let ``inferType handles multiple root elements`` () =
238+ let elements = [|
239+ createElement """ <person name="John">Developer</person>"""
240+ createElement """ <person name="Jane">Designer</person>"""
241+ |]
242+ let result = XmlInference.inferType unitsOfMeasureProvider inferenceMode culture allowEmptyValues false elements
243+
244+ result.Length |> should equal 2
245+ // Both should be person records
246+ result |> Array.forall ( function
247+ | InferedType.Record( Some " person" , _, false ) -> true
248+ | _ -> false ) |> should equal true
249+
250+ [<Test>]
251+ let ``XML with complex nested structure infers correctly`` () =
252+ let element = createElement """
253+ <library name="City Library">
254+ <book id="1" category="fiction">
255+ <title>The Great Gatsby</title>
256+ <author>F. Scott Fitzgerald</author>
257+ <year>1925</year>
258+ <available>true</available>
259+ </book>
260+ <book id="2" category="science">
261+ <title>A Brief History of Time</title>
262+ <author>Stephen Hawking</author>
263+ <year>1988</year>
264+ <available>false</available>
265+ </book>
266+ </library>
267+ """
268+
269+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
270+
271+ match result with
272+ | InferedType.Record( Some name, properties, false ) ->
273+ name |> should equal " library"
274+ properties |> List.exists ( fun p -> p.Name = " name" ) |> should equal true
275+ properties |> List.exists ( fun p -> p.Name = " " ) |> should equal true // Collection of books
276+ | _ -> failwith " Expected library record with books collection"
277+
278+ [<Test>]
279+ let ``XML with mixed content types infers correctly`` () =
280+ let element = createElement """
281+ <data>
282+ <number>42</number>
283+ <text>Hello</text>
284+ <flag>true</flag>
285+ <price>19.99</price>
286+ </data>
287+ """
288+
289+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
290+
291+ match result with
292+ | InferedType.Record( Some name, properties, false ) ->
293+ name |> should equal " data"
294+ properties.Length |> should equal 1 // Collection of mixed elements
295+ match properties.[ 0 ]. Type with
296+ | InferedType.Collection(_, _) -> () // Success
297+ | _ -> failwith " Expected collection of mixed elements"
298+ | _ -> failwith " Expected data record with mixed content"
299+
300+ [<Test>]
301+ let ``XML with namespaced elements handles correctly`` () =
302+ let element = createElement """ <root xmlns:ns="http://example.com"><ns:item ns:value="test">content</ns:item></root>"""
303+
304+ let result = XmlInference.inferLocalType unitsOfMeasureProvider inferenceMode culture allowEmptyValues element
305+
306+ match result with
307+ | InferedType.Record( Some name, _, false ) ->
308+ name |> should equal " root"
309+ | _ -> failwith " Expected root record type for namespaced XML"
310+
311+ [<Test>]
312+ let ``XML inference handles large values correctly`` () =
313+ let element = createElement """ <data>9223372036854775807</data>""" // Int64.MaxValue
314+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
315+
316+ match result with
317+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< int64>
318+ | _ -> failwith " Expected int64 type for large integer"
319+
320+ [<Test>]
321+ let ``XML inference handles floating point values`` () =
322+ let element = createElement """ <value>123.456789</value>"""
323+ let result = XmlInference.getInferedTypeFromValue unitsOfMeasureProvider inferenceMode culture element
324+
325+ match result with
326+ | InferedType.Primitive( t, _, _, _) -> t |> should equal typeof< decimal>
327+ | _ -> failwith " Expected decimal type for floating point"
0 commit comments