3

I have developed an F# REST service framework which has recently gone into production usage, and one of the follow-up tools I'm working on is an automatic OpenAPI spec generator. The generator uses the FSharp.Compiler.Services library to examine each function exposed as a service entry point and generate the API spec, including schemas for each parameter. These parameters are often single-case union types which use a special rules computation expression to define validation rules, like the following:

type AccoutNubmerValidationError =
| AccountNumberMustBeTenDigitsLong of string
| AccountNumberMustBeNumerical of string

/// Account Number must be a 10-digit numerical string
[<Struct; Validated>] type AccoutNumber = private AccountNumber of string

module AccountNumber = 
    let create : string -> Result<AccountNumber, AccountNumberValidationError> =
        rules {
            rule (Rules.length 10) AccountNumberBeTenDigitsLong
            rule (Rules.pattern "^[\d]{10}$") AccountNumberMustBeNumerical
        }

I would like to parse each rule in the computation expression (which is a CustomOperation on the RulesBuilder class) so I can generate the correct values for length, pattern, etc. on the OpenApi schema for AccountNumber. While I can identify the correct binding and I have the FSharpExpr for the body, I haven't figured out exactly what I would need to do to parse that expression and extract the values for each rule.

If there is any documentation available on how to parse the bodies of computation expressions using F# Compiler Services, I haven't yet been able to find it. Does anyone have any experience they can share to help me figure it out?

EDIT

I received a close vote for 'asking for a tutorial', so I want to clarify my problem. I have an instance of an FSharpMemberOrFunctionOrValue for the create binding in the example above, as well as an FSharpExpr for the body of the expression. The expression looks something like this (represented as JSON to make it readable):

        {
          "Type": "FSharpImplementationFileDeclaration.MemberOrFunctionOrValue",
          "Range": {
            "StartLine": 15,
            "StartCol": 4,
            "EndLine": 15,
            "EndCol": 9
          },
          "Properties": [
            [
              "v",
              "val create"
            ],
            [
              "vs",
              []
            ]
          ],
          "Childs": [
            {
              "Type": "BasicPatterns.Application",
              "Range": {
                "StartLine": 15,
                "StartCol": 4,
                "EndLine": 15,
                "EndCol": 9
              },
              "Properties": [],
              "Childs": [
                {
                  "Type": "BasicPatterns.Lambda",
                  "Range": {
                    "StartLine": 15,
                    "StartCol": 4,
                    "EndLine": 15,
                    "EndCol": 9
                  },
                  "Properties": [
                    [
                      "lambdaVar",
                      "val builder@"
                    ]
                  ],
                  "Childs": [
                    {
                      "Type": "BasicPatterns.Call",
                      "Range": {
                        "StartLine": 17,
                        "StartCol": 8,
                        "EndLine": 17,
                        "EndCol": 64
                      },
                      "Properties": [
                        [
                          "memberOrFunc",
                          "member Require"
                        ],
                        [
                          "typeArg2",
                          "type Microsoft.FSharp.Core.string"
                        ]
                      ],
                      "Childs": [
                        {
                          "Type": "BasicPatterns.Value",
                          "Range": {
                            "StartLine": 17,
                            "StartCol": 8,
                            "EndLine": 17,
                            "EndCol": 64
                          },
                          "Properties": [
                            [
                              "valueToGet",
                              "val builder@"
                            ]
                          ],
                          "Childs": []
                        },
                        {
                          "Type": "BasicPatterns.Call",
                          "Range": {
                            "StartLine": 16,
                            "StartCol": 8,
                            "EndLine": 16,
                            "EndCol": 63
                          },
                          "Properties": [
                            [
                              "memberOrFunc",
                              "member Require"
                            ],
                            [
                              "typeArg2",
                              "type Microsoft.FSharp.Core.string"
                            ]
                          ],
                          "Childs": [
                            {
                              "Type": "BasicPatterns.Value",
                              "Range": {
                                "StartLine": 16,
                                "StartCol": 8,
                                "EndLine": 16,
                                "EndCol": 63
                              },
                              "Properties": [
                                [
                                  "valueToGet",
                                  "val builder@"
                                ]
                              ],
                              "Childs": []
                            },
                            {
                              "Type": "BasicPatterns.Call",
                              "Range": {
                                "StartLine": 16,
                                "StartCol": 8,
                                "EndLine": 16,
                                "EndCol": 63
                              },
                              "Properties": [
                                [
                                  "memberOrFunc",
                                  "member Yield"
                                ],
                                [
                                  "typeArg2",
                                  "type Microsoft.FSharp.Core.obj * Microsoft.FSharp.Core.string"
                                ]
                              ],
                              "Childs": [
                                {
                                  "Type": "BasicPatterns.Value",
                                  "Range": {
                                    "StartLine": 16,
                                    "StartCol": 8,
                                    "EndLine": 16,
                                    "EndCol": 63
                                  },
                                  "Properties": [
                                    [
                                      "valueToGet",
                                      "val builder@"
                                    ]
                                  ],
                                  "Childs": []
                                },
                                {
                                  "Type": "BasicPatterns.Const",
                                  "Range": {
                                    "StartLine": 16,
                                    "StartCol": 8,
                                    "EndLine": 16,
                                    "EndCol": 63
                                  },
                                  "Properties": [
                                    [
                                      "constType",
                                      "type Microsoft.FSharp.Core.unit"
                                    ],
                                    [
                                      "constValueObj",
                                      null
                                    ]
                                  ],
                                  "Childs": []
                                }
                              ]
                            },
                            {
                              "Type": "BasicPatterns.Call",
                              "Range": {
                                "StartLine": 16,
                                "StartCol": 17,
                                "EndLine": 16,
                                "EndCol": 32
                              },
                              "Properties": [
                                [
                                  "memberOrFunc",
                                  "val raise"
                                ],
                                [
                                  "typeArg2",
                                  "type Microsoft.FSharp.Core.obj"
                                ]
                              ],
                              "Childs": [
                                {
                                  "Type": "BasicPatterns.Const",
                                  "Range": {
                                    "StartLine": 16,
                                    "StartCol": 17,
                                    "EndLine": 16,
                                    "EndCol": 32
                                  },
                                  "Properties": [
                                    [
                                      "constType",
                                      "type Microsoft.FSharp.Core.int32"
                                    ],
                                    [
                                      "constValueObj",
                                      1
                                    ]
                                  ],
                                  "Childs": []
                                }
                              ]
                            },
                            {
                              "Type": "BasicPatterns.Const",
                              "Range": {
                                "StartLine": 16,
                                "StartCol": 34,
                                "EndLine": 16,
                                "EndCol": 63
                              },
                              "Properties": [
                                [
                                  "constType",
                                  "type Microsoft.FSharp.Core.string"
                                ],
                                [
                                  "constValueObj",
                                  "Must be 10 digits in length"
                                ]
                              ],
                              "Childs": []
                            }
                          ]
                        },
                        {
                          "Type": "BasicPatterns.Call",
                          "Range": {
                            "StartLine": 17,
                            "StartCol": 17,
                            "EndLine": 17,
                            "EndCol": 43
                          },
                          "Properties": [
                            [
                              "memberOrFunc",
                              "val raise"
                            ],
                            [
                              "typeArg2",
                              "type Microsoft.FSharp.Core.obj"
                            ]
                          ],
                          "Childs": [
                            {
                              "Type": "BasicPatterns.Const",
                              "Range": {
                                "StartLine": 17,
                                "StartCol": 17,
                                "EndLine": 17,
                                "EndCol": 43
                              },
                              "Properties": [
                                [
                                  "constType",
                                  "type Microsoft.FSharp.Core.int32"
                                ],
                                [
                                  "constValueObj",
                                  1
                                ]
                              ],
                              "Childs": []
                            }
                          ]
                        },
                        {
                          "Type": "BasicPatterns.Const",
                          "Range": {
                            "StartLine": 17,
                            "StartCol": 45,
                            "EndLine": 17,
                            "EndCol": 64
                          },
                          "Properties": [
                            [
                              "constType",
                              "type Microsoft.FSharp.Core.string"
                            ],
                            [
                              "constValueObj",
                              "Must be numerical"
                            ]
                          ],
                          "Childs": []
                        }
                      ]
                    }
                  ]
                },
                {
                  "Type": "BasicPatterns.Call",
                  "Range": {
                    "StartLine": 15,
                    "StartCol": 4,
                    "EndLine": 15,
                    "EndCol": 9
                  },
                  "Properties": [
                    [
                      "memberOrFunc",
                      "val rules"
                    ]
                  ],
                  "Childs": []
                }
              ]
            }
          ]
        }

The problem is, I don't even see a reference to Rules.length or Rules.pattern in this expression tree. Presumably, this must be because I'm creating some intermediate partially-applied function that's represented elsewhere in the expression tree for the program. Is there a deterministic way to identify which portions of the expression tree I need to navigate and then find the actual constant values used in the partially-applied functions so that I can reliably use them programmatically?

Aaron M. Eshbach
  • 6,380
  • 12
  • 22

0 Answers0