Skip to content

Commit be43c77

Browse files
Repo AssistCopilot
authored andcommitted
Add YamlProvider type provider
Implements a YamlProvider that enables typed access to YAML documents, reusing the existing JSON inference and code generation infrastructure. Architecture: - New FSharp.Data.Yaml.Core project: contains YamlDocument (implements IJsonDocument) and YamlConversions (YAML → JsonValue via YamlDotNet) - YamlProvider in FSharp.Data.DesignTime: reuses JsonInference and JsonGenerator; parses YAML samples to JsonValue at design time - YamlDotNet 16.3 added as dependency for robust YAML 1.2 parsing YAML-to-JsonValue mapping: - Mappings → JsonValue.Record - Sequences → JsonValue.Array - Quoted scalars → JsonValue.String (preserves string intent) - Plain scalars: null/~/bool/int/float auto-detected per YAML core schema Supported static parameters: Sample, SampleIsList, RootName, Culture, Encoding, ResolutionFolder, EmbeddedResource, InferTypesFromValues, PreferDictionaries, InferenceMode, PreferDateOnly, UseOriginalNames Closes #1645 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e7251a0 commit be43c77

15 files changed

Lines changed: 613 additions & 4 deletions

FSharp.Data.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Data.Core.Tests.CSha
3636
EndProject
3737
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Json.Core", "src\FSharp.Data.Json.Core\FSharp.Data.Json.Core.fsproj", "{DAEBFBCF-84CD-40BB-B8F6-99B1A9C4641F}"
3838
EndProject
39+
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Yaml.Core", "src\FSharp.Data.Yaml.Core\FSharp.Data.Yaml.Core.fsproj", "{B2C3D4E5-6F7A-8901-BCDE-F12345678901}"
40+
EndProject
3941
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Tests", "tests\FSharp.Data.Tests\FSharp.Data.Tests.fsproj", "{750148EC-6A05-421D-96A4-E5AC9D18AF58}"
4042
EndProject
4143
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.Benchmarks", "tests\FSharp.Data.Benchmarks\FSharp.Data.Benchmarks.fsproj", "{A1B2C3D4-5E6F-7890-ABCD-EF1234567890}"
@@ -128,6 +130,10 @@ Global
128130
{DAEBFBCF-84CD-40BB-B8F6-99B1A9C4641F}.Debug|Any CPU.Build.0 = Debug|Any CPU
129131
{DAEBFBCF-84CD-40BB-B8F6-99B1A9C4641F}.Release|Any CPU.ActiveCfg = Release|Any CPU
130132
{DAEBFBCF-84CD-40BB-B8F6-99B1A9C4641F}.Release|Any CPU.Build.0 = Release|Any CPU
133+
{B2C3D4E5-6F7A-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
134+
{B2C3D4E5-6F7A-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
135+
{B2C3D4E5-6F7A-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
136+
{B2C3D4E5-6F7A-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
131137
{750148EC-6A05-421D-96A4-E5AC9D18AF58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
132138
{750148EC-6A05-421D-96A4-E5AC9D18AF58}.Debug|Any CPU.Build.0 = Debug|Any CPU
133139
{750148EC-6A05-421D-96A4-E5AC9D18AF58}.Release|Any CPU.ActiveCfg = Release|Any CPU

paket.dependencies

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ github fsprojects/FSharp.TypeProviders.SDK src/ProvidedTypes.fs
1212
github fsprojects/FSharp.TypeProviders.SDK tests/ProvidedTypesTesting.fs
1313

1414
nuget FSharp.Core >= 6.0.1 lowest_matching: true
15+
nuget YamlDotNet >= 13.0.0
1516
nuget Microsoft.SourceLink.GitHub 1.0 copy_local: true
1617
nuget Microsoft.SourceLink.Common 1.0 copy_local: true
1718
nuget Microsoft.Build.Tasks.Git 1.0 copy_local: true

paket.lock

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ NUGET
2222
NETStandard.Library (2.0.3) - restriction: || (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.2))
2323
Microsoft.NETCore.Platforms (>= 1.1)
2424
NETStandard.Library.NETFramework (2.0.0-preview2-25405-01)
25+
YamlDotNet (16.3)
2526
GITHUB
2627
remote: fsprojects/FSharp.TypeProviders.SDK
27-
src/ProvidedTypes.fs (ce34c1cc71096857b8342f1dedf93391addc9df6)
28-
src/ProvidedTypes.fsi (ce34c1cc71096857b8342f1dedf93391addc9df6)
29-
tests/ProvidedTypesTesting.fs (ce34c1cc71096857b8342f1dedf93391addc9df6)
28+
src/ProvidedTypes.fs (90e8e3532960bffc71198fc12a01761a1f39624d)
29+
src/ProvidedTypes.fsi (90e8e3532960bffc71198fc12a01761a1f39624d)
30+
tests/ProvidedTypesTesting.fs (90e8e3532960bffc71198fc12a01761a1f39624d)
3031
GROUP Benchmarks
3132
RESTRICTION: == net8.0
3233
NUGET

src/AssemblyInfo.Yaml.Core.fs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Auto-Generated by FAKE; do not edit
2+
namespace System
3+
4+
open System.Reflection
5+
6+
[<assembly: AssemblyTitleAttribute("FSharp.Data.Yaml.Core")>]
7+
[<assembly: AssemblyProductAttribute("FSharp.Data")>]
8+
[<assembly: AssemblyDescriptionAttribute("Library of F# type providers and data access tools")>]
9+
[<assembly: AssemblyVersionAttribute("6.6.0.0")>]
10+
[<assembly: AssemblyFileVersionAttribute("6.6.0.0")>]
11+
do ()
12+
13+
module internal AssemblyVersionInformation =
14+
[<Literal>]
15+
let AssemblyTitle = "FSharp.Data.Yaml.Core"
16+
17+
[<Literal>]
18+
let AssemblyProduct = "FSharp.Data"
19+
20+
[<Literal>]
21+
let AssemblyDescription = "Library of F# type providers and data access tools"
22+
23+
[<Literal>]
24+
let AssemblyVersion = "6.6.0.0"
25+
26+
[<Literal>]
27+
let AssemblyFileVersion = "6.6.0.0"

src/FSharp.Data.DesignTime/FSharp.Data.DesignTime.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
<Compile Include="WorldBank/WorldBankProvider.fs" />
3333
<Compile Include="Html/HtmlGenerator.fs" />
3434
<Compile Include="Html/HtmlProvider.fs" />
35+
<Compile Include="Yaml/YamlProvider.fs" />
3536
<Compile Include="../AssemblyInfo.DesignTime.fs" />
3637
<None Include="../Test.fsx" />
3738
<None Include="paket.references" />
3839
</ItemGroup>
3940
<ItemGroup>
4041
<ProjectReference Include="../FSharp.Data.Json.Core/FSharp.Data.Json.Core.fsproj" />
42+
<ProjectReference Include="../FSharp.Data.Yaml.Core/FSharp.Data.Yaml.Core.fsproj" />
4143
<ProjectReference Include="../FSharp.Data.Xml.Core/FSharp.Data.Xml.Core.fsproj" />
4244
<ProjectReference Include="../FSharp.Data.Csv.Core/FSharp.Data.Csv.Core.fsproj" />
4345
<ProjectReference Include="../FSharp.Data.Html.Core/FSharp.Data.Html.Core.fsproj" />
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
namespace ProviderImplementation
2+
3+
open System
4+
open System.IO
5+
open FSharp.Core.CompilerServices
6+
open ProviderImplementation
7+
open ProviderImplementation.ProvidedTypes
8+
open ProviderImplementation.ProviderHelpers
9+
open FSharp.Data
10+
open FSharp.Data.Runtime
11+
open FSharp.Data.Runtime.BaseTypes
12+
open FSharp.Data.Runtime.StructuralTypes
13+
open FSharp.Data.Runtime.StructuralInference
14+
open System.Net
15+
16+
// ----------------------------------------------------------------------------------------------
17+
18+
#nowarn "10001"
19+
20+
[<TypeProvider>]
21+
type public YamlProvider(cfg: TypeProviderConfig) as this =
22+
inherit
23+
DisposableTypeProviderForNamespaces(cfg, assemblyReplacementMap = [ "FSharp.Data.DesignTime", "FSharp.Data" ])
24+
25+
// Generate namespace and type 'FSharp.Data.YamlProvider'
26+
do AssemblyResolver.init ()
27+
let asm = System.Reflection.Assembly.GetExecutingAssembly()
28+
let ns = "FSharp.Data"
29+
30+
let yamlProvTy =
31+
ProvidedTypeDefinition(asm, ns, "YamlProvider", None, hideObjectMethods = true, nonNullable = true)
32+
33+
let buildTypes (typeName: string) (args: obj[]) =
34+
35+
// Enable TLS 1.2 for samples requested through https.
36+
ServicePointManager.SecurityProtocol <- ServicePointManager.SecurityProtocol ||| SecurityProtocolType.Tls12
37+
38+
// Generate the required type
39+
let tpType =
40+
ProvidedTypeDefinition(asm, ns, typeName, None, hideObjectMethods = true, nonNullable = true)
41+
42+
let sample = args.[0] :?> string
43+
let sampleIsList = args.[1] :?> bool
44+
let rootName = args.[2] :?> string
45+
46+
let rootName =
47+
if String.IsNullOrWhiteSpace rootName then
48+
"Root"
49+
else
50+
NameUtils.singularize rootName
51+
52+
let cultureStr = args.[3] :?> string
53+
let encodingStr = args.[4] :?> string
54+
let resolutionFolder = args.[5] :?> string
55+
let resource = args.[6] :?> string
56+
let inferTypesFromValues = args.[7] :?> bool
57+
let preferDictionaries = args.[8] :?> bool
58+
let inferenceMode = args.[9] :?> InferenceMode
59+
let preferDateOnly = args.[10] :?> bool
60+
let useOriginalNames = args.[11] :?> bool
61+
62+
let inferenceMode =
63+
InferenceMode'.FromPublicApi(inferenceMode, inferTypesFromValues)
64+
65+
let cultureInfo = TextRuntime.GetCulture cultureStr
66+
let unitsOfMeasureProvider = ProviderHelpers.unitsOfMeasureProvider
67+
68+
let getSpec _ value =
69+
70+
let inferedType =
71+
use _holder = IO.logTime "Inference" sample
72+
73+
// Parse YAML sample into JsonValue, then use JSON inference
74+
let rawInfered =
75+
let samples =
76+
use _holder = IO.logTime "Parsing" sample
77+
78+
if sampleIsList then
79+
// If SampleIsList, parse as a YAML sequence or multiple documents
80+
match YamlDocument.ParseToJsonValue value with
81+
| JsonValue.Array items -> items
82+
| single -> [| single |]
83+
else
84+
[| YamlDocument.ParseToJsonValue value |]
85+
86+
samples
87+
|> Array.map (fun sampleJson ->
88+
JsonInference.inferType unitsOfMeasureProvider inferenceMode cultureInfo "" sampleJson)
89+
|> Array.fold (StructuralInference.subtypeInfered false) InferedType.Top
90+
91+
#if NET6_0_OR_GREATER
92+
if preferDateOnly && ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly then
93+
rawInfered
94+
else
95+
StructuralInference.downgradeNet6Types rawInfered
96+
#else
97+
rawInfered
98+
#endif
99+
100+
use _holder = IO.logTime "TypeGeneration" sample
101+
102+
let ctx =
103+
JsonGenerationContext.Create(
104+
cultureStr,
105+
tpType,
106+
unitsOfMeasureProvider,
107+
inferenceMode,
108+
?preferDictionaries = Some preferDictionaries,
109+
?useOriginalNames = Some useOriginalNames
110+
)
111+
112+
let result = JsonTypeBuilder.generateJsonType ctx false false rootName inferedType
113+
114+
{ GeneratedType = tpType
115+
RepresentationType = result.ConvertedType
116+
CreateFromTextReader = fun reader -> result.Convert <@@ YamlDocument.Create(%reader) @@>
117+
CreateListFromTextReader = Some(fun reader -> result.Convert <@@ YamlDocument.CreateList(%reader) @@>)
118+
CreateFromTextReaderForSampleList = fun reader -> result.Convert <@@ YamlDocument.CreateList(%reader) @@>
119+
CreateFromValue =
120+
Some(typeof<JsonValue>, (fun value -> result.Convert <@@ YamlDocument.Create(%value, "") @@>)) }
121+
122+
let source = if sampleIsList then SampleList sample else Sample sample
123+
124+
generateType "YAML" source getSpec this cfg encodingStr resolutionFolder resource typeName None
125+
126+
// Add static parameter that specifies the API we want to get (compile-time)
127+
let parameters =
128+
[ ProvidedStaticParameter("Sample", typeof<string>, parameterDefaultValue = "")
129+
ProvidedStaticParameter("SampleIsList", typeof<bool>, parameterDefaultValue = false)
130+
ProvidedStaticParameter("RootName", typeof<string>, parameterDefaultValue = "Root")
131+
ProvidedStaticParameter("Culture", typeof<string>, parameterDefaultValue = "")
132+
ProvidedStaticParameter("Encoding", typeof<string>, parameterDefaultValue = "")
133+
ProvidedStaticParameter("ResolutionFolder", typeof<string>, parameterDefaultValue = "")
134+
ProvidedStaticParameter("EmbeddedResource", typeof<string>, parameterDefaultValue = "")
135+
ProvidedStaticParameter("InferTypesFromValues", typeof<bool>, parameterDefaultValue = true)
136+
ProvidedStaticParameter("PreferDictionaries", typeof<bool>, parameterDefaultValue = false)
137+
ProvidedStaticParameter(
138+
"InferenceMode",
139+
typeof<InferenceMode>,
140+
parameterDefaultValue = InferenceMode.BackwardCompatible
141+
)
142+
ProvidedStaticParameter("PreferDateOnly", typeof<bool>, parameterDefaultValue = false)
143+
ProvidedStaticParameter("UseOriginalNames", typeof<bool>, parameterDefaultValue = false) ]
144+
145+
let helpText =
146+
"""<summary>Typed representation of a YAML document.</summary>
147+
<param name='Sample'>Location of a YAML sample file or a string containing a sample YAML document.</param>
148+
<param name='SampleIsList'>If true, the sample should be a YAML sequence (list) and each element is used as an individual sample for type inference.</param>
149+
<param name='RootName'>The name to be used for the root type. Defaults to `Root`.</param>
150+
<param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param>
151+
<param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 for HTTP requests, unless `charset` is specified in the `Content-Type` response header.</param>
152+
<param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param>
153+
<param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource
154+
(e.g. 'MyCompany.MyAssembly, resource_name.yaml'). This is useful when exposing types generated by the type provider.</param>
155+
<param name='InferTypesFromValues'>
156+
This parameter is deprecated. Please use InferenceMode instead.
157+
If true, turns on additional type inference from values.
158+
(e.g. type inference infers string values such as "123" as ints and values constrained to 0 and 1 as booleans.)</param>
159+
<param name='PreferDictionaries'>If true, YAML mappings are interpreted as dictionaries when the names of all the fields are inferred (by type inference rules) into the same non-string primitive type.</param>
160+
<param name='InferenceMode'>Possible values:
161+
| NoInference -> Inference is disabled. All values are inferred as the most basic type permitted for the value (i.e. string or number or bool).
162+
| ValuesOnly -> Types of values are inferred from the Sample. This is the default.
163+
| ValuesAndInlineSchemasHints -> Types of values are inferred from both values and inline schemas. Inline schemas are special string values that can define a type and/or unit of measure.
164+
| ValuesAndInlineSchemasOverrides -> Same as ValuesAndInlineSchemasHints, but value inferred types are ignored when an inline schema is present.
165+
</param>
166+
<param name='PreferDateOnly'>When true on .NET 6+, date-only strings (e.g. "2023-01-15") are inferred as DateOnly and time-only strings as TimeOnly. Defaults to false for backward compatibility.</param>
167+
<param name='UseOriginalNames'>When true, YAML key names are used as-is for generated property names instead of being normalized to PascalCase. Defaults to false.</param>"""
168+
169+
do yamlProvTy.AddXmlDoc helpText
170+
do yamlProvTy.DefineStaticParameters(parameters, buildTypes)
171+
172+
// Register the main type with F# compiler
173+
do this.AddNamespace(ns, [ yamlProvTy ])
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
Microsoft.SourceLink.GitHub
2-
FSharp.Core
2+
FSharp.Core
3+
YamlDotNet
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<OutputType>Library</OutputType>
5+
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
6+
<OtherFlags>$(OtherFlags) --warnon:1182 --nowarn:10001 --nowarn:44</OtherFlags>
7+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
8+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
9+
<PackageIcon>logo.png</PackageIcon>
10+
<Tailcalls>true</Tailcalls>
11+
</PropertyGroup>
12+
<ItemGroup>
13+
<Compile Include="YamlDocument.fs" />
14+
<Compile Include="../AssemblyInfo.Yaml.Core.fs" />
15+
<None Include="../../docs/img/logo.png" Pack="true" PackagePath="" />
16+
<None Include="paket.references" />
17+
</ItemGroup>
18+
<ItemGroup>
19+
<ProjectReference Include="../FSharp.Data.Json.Core/FSharp.Data.Json.Core.fsproj" />
20+
<ProjectReference Include="../FSharp.Data.Runtime.Utilities/FSharp.Data.Runtime.Utilities.fsproj" />
21+
</ItemGroup>
22+
<Import Project="..\..\.paket\Paket.Restore.targets" />
23+
</Project>

0 commit comments

Comments
 (0)