Bug? FSharp.GrpcCodeGenerator fails on timestamp of 1970-01-01 - f#

I have a standard protobuf file on both client and server.
syntax = "proto3";
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
package Protocol.ProtoBuf;
service PatientService {
// Get all patient names
rpc GetAllPatients(GetAllPatientsRequest) returns (PatientListResponse);
}
message Patient {
int32 PatientId =1;
google.protobuf.Timestamp BirthDate = 2;
string FirstName =3;
string LastName=4;
google.protobuf.StringValue Mi=5; // StringValue is the protobuf property type for Nullable<string>.
int32 ChartNumber=6;
}
message GetAllPatientsRequest {}
message PatientListResponse {
repeated Patient Patients = 1;
}
When GetAllPatients is tested on the server, it correctly returns the BirthDate as:
Google.Protobuf.WellKnownTypes.Timestamp Patient.BirthDate with a value of {"1970-01-01T00.00.00Z"}
However, in my client project when using FSharp.GrpcCodeGenerator to directly compile the proto file to F#, the returned value from the server shows the BirthDate field to have a ValueOption of ValueNone--that is I am not getting the BirthDate timestamp on the client side. (I am, however, getting all the other values).
How can I download the TimeStamp in F#? What am I missing?
Thanks for any help on this.
The client project is defined as:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\patient.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc-FSharp.Net.Client" Version="0.1.1" />
<PackageReference Include="Grpc-FSharp.Tools" Version="0.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Addendum: Further testing shows that all birthdates are correctly downloaded except for 1970-01-01 which is being translated to a null value on the client. (The server does correctly send 1970-01-01).

The only way I could get this to work correctly for 1970-01-01 (in f#) is:
let patientToDomain (x:Protocol.ProtoBuf.Patient) : Patient = {PatientId = match x.PatientId with
| ValueNone -> failwith "Missing Patient Id"
| ValueSome d -> d;
BirthDate = match x.BirthDate with
| ValueNone -> failwith "Missing BirthDate"
| ValueSome d -> match d.ToDateTime() with
| ValueNone -> DateTime(1970,01,01)
| ValueSome f -> f
FirstName = match x.FirstName with
| ValueNone -> failwith "Missing FirstName"
| ValueSome s -> s;
LastName = match x.LastName with
| ValueNone -> failwith "Missing LastName"
| ValueSome s -> s;
Mi = match x.Mi with
| ValueNone -> ""
| ValueSome s -> if s.Value.IsNone
then ""
else s.Value.ToString();
ChartNumber = match x.ChartNumber with
| ValueNone -> failwith "Missing ChartNumber"
| ValueSome i -> i}
That is, substitute 1970-01-01 when null is detected. Any Better Ideas?

Related

Error using bool.Parse on null/empty values

I have an expression using pipe operator that converts the value to string and then to bool, however sometimes the original value can be null.
How can I use the pattern matching or something else to assume false when the value is null?
type kv = Dictionary<string, obj>
let allDayEvent (d: kv) = d.["fAllDayEvent"] |> string |> bool.Parse
There's quite a few places where you can safeguard via pattern matching: dictionary lookup, casting, parsing. Here's an example with all of those:
let allDayEvent (d: kv) =
match d.TryGetValue "fAllDayEvent" with
| true, v ->
match v with
| null -> printfn "null found"
| :? string as s ->
match bool.TryParse s with
| true, b -> printfn "found a bool: %A" b
| _ -> printfn "That's not a bool?"
| v -> printfn "Found something of type %s" (v.GetType().Name)
| _ -> printfn "No such key"
See also related questions, for example this.
Not sure why you are using a Dictionary, but I would probably have gone for a Map instead. Or at least done some Conversion to Map somewhere. And then I would maybe have thrown in some "automagically" handling of nulls.
And then Pandoras Box is kind of opened, but....
let (|Bool|) str =
match System.Boolean.TryParse(str) with
| (true,bool) -> Some(bool)
| _ -> None
let (|String|) (o:obj) =
match o with
| :? string as s -> Some(s)
| _ -> None
type kv = Dictionary<string, obj>
let allDayEvent (d: kv) =
d :> seq<_>
|> Seq.map (|KeyValue|)
|> Map.ofSeq
|> Map.tryFind "fAllDayEvent"
|> Option.bind (|String|)
|> Option.bind (|Bool|)
Note that allDayEvent in the above now is an Option, which maybe is in fact what you need/want.
And it does keep all data in place. Like true or false is not the same as "did not find stuff" or "could not convert stuff to some bool". Now it is in fact one of the following:
key found and some string like "true": Some(true)
key found and some string like "false": Some(false)
key not found or string not convertable to bool: None
Code is not tested and may need some further massaging.

Questions on tuples and user input in F#

I'm trying to play around with f# to get down the basics but stuck. If you have general tips it would be greatly appreciated.
#light
open System
[<EntryPoint>]
let main (args : string[]) =
match args with
| [| firstName; lastName; city |] ->
printfn "Hi there %s %s from %s" firstName lastName city
0
| _ -> failwith "Usage: HiThere.exe firstName lastName City";;
I get this error,
val main : args:string [] -> int
I was watching a tutorial on f# and trying to learn this function but I don't understand why I can't do it without these errors.
There is a very minor typo in args.[0], args.[1]. args.[2]. You have a . rather than a , between the second and third element - it should be args.[0], args.[1], args.[2].
The compiler is still able to parse it, but it interprets your code as:
args.[0], (args.[1].args.[2])
This is syntactically a two-element tuple and you are assigning it to a three-element tuple, so you get the error about tuples first. It would fail later because args is not a member of args.[1], but that's a separate message (that the compiler ignores because it reports the one it finds earlier).
As a side-note, you could also use pattern matching on arrays and write:
let main (args : string[]) =
match args with
| [| firstName; lastName; city |] ->
printfn "Hi there %s %s from %s" firstName lastName city
0
| _ -> failwith "Usage: HiThere.exe firstName lastName City"

Active patterns over provided types in F#

I have a scenario where I am using the XML type provider from FSharp.Data to read in a stream containing various key/value pairs. The values in this case are sometimes decimals, sometimes dates, and sometimes strings:
<records>
<fields>
<key>foo</key>
<value>123.456</value>
</fields>
<fields>
<key>bar</key>
<value>2013-07-23</value>
</fields>
<fields>
<key>fizz</key>
<value>hello world</value>
</fields>
</records>
Because there seems to be no way to refer to the provided types by name, in order to pattern match on the values I have to move the values into a tuple and then provide active patterns over that:
open System
open System.IO
type XmlFoo=FSharp.Data.XmlProvider<"""<records>
<fields>
<key>foo</key>
<value>123.456</value>
</fields>
<fields>
<key>bar</key>
<value>2013-07-23</value>
</fields>
<fields>
<key>fizz</key>
<value>hello world</value>
</fields>
</records>""">
[<EntryPoint>]
let main argv =
let (|Date|_|) ((v,_,_):Option<DateTime> * Option<Decimal> * Option<String>) = v
let (|Number|_|) ((_,v,_):Option<DateTime> * Option<Decimal> * Option<String>) = v
let (|String|_|) ((_,_,v):Option<DateTime> * Option<Decimal> * Option<String>) = v
use stream = File.OpenRead("sample.xml")
let data = XmlFoo.Load(stream)
for field in data.Fields do
let value = field.Value.DateTimeValue, field.Value.NumberValue, field.Value.StringValue
match value with
| Date x -> printfn "Found a date: %s" (x.ToShortDateString())
| Number x -> printfn "Found a number: %M" x
| String x -> printfn "Found a string: %s" x
| _ -> printfn "Found nothing"
0 // return an integer exit code
Is there a way either to
a) refer to the provided types directly (so I don't need the intermediate tuple and the active patterns can be generalised to XML files that return any number of possible data types)
or
b) modify the type provider so that it provides the active patterns itself - not sure if this is possible looking at the type provider API.
EDIT: Nevermind, I missed the obvious - the tooltips in VS2013 are misleading. By displaying <...> it gives the impression there isn't a way to refer to the type by name. Thanks to #ovatsus for the tip. You can do this:
open System
open System.IO
type XmlFoo=FSharp.Data.XmlProvider<"""<records>
<fields>
<key>foo</key>
<value>123.456</value>
</fields>
<fields>
<key>bar</key>
<value>2013-07-23</value>
</fields>
<fields>
<key>fizz</key>
<value>hello world</value>
</fields>
</records>""">
[<EntryPoint>]
let main argv =
let (|Date|_|) (v:XmlFoo.Value) = v.DateTimeValue
let (|Number|_|) (v:XmlFoo.Value) = v.NumberValue
let (|String|_|) (v:XmlFoo.Value) = v.StringValue
use stream = File.OpenRead("sample.xml")
let data = XmlFoo.Load(stream)
for field in data.Fields do
match field.Value with
| Date x -> printfn "Found a date: %s" (x.ToShortDateString())
| Number x -> printfn "Found a number: %M" x
| String x -> printfn "Found a string: %s" x
| _ -> printfn "Found nothing"
0 // return an integer exit code
You can create type aliases to reference the types directly:
type F = XmlFoo.Field
For the question about having active patterns directly in the type provider, it seems that active patterns must be let bindings, not class members (even static), so there is no way to provide them unfortunately.

F# match pattern for Expr<int>

I try to find the correct pattern to match and run an Expr<int> using the below code:
open System.Linq
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
let runSelectQuery (q:Expr<IQueryable<'T>>) =
match q with
| Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
let runCountQuery (q:Expr<int>) =
match q with
| Application(Lambda(builder, Call(None, miRun, [builder2, Quote body])), queryObj) ->
query.Run(Expr.Cast<int>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
let countQuery source filter =
let filter = match filter with | Some filter -> filter | _ -> <# fun _ -> true #>
<# query { for item in source do
where ((%filter) item)
count } #>
The runSelectQuery correctly matches the Expr<IQueryable<'T>> pattern. However, I cannot find the correct pattern to match my generic count query Expr<int>
The pattern in the code I derived from the signature of countQuery gives me a:
This expression was expected to have type
Expr but here has type
'a * 'b
Found it! Stupidly I first tried to match the array pattern using a comma separated pattern (as is the list delimiter in C#), that obviously did not work in F# complaining the it was not a list but a tupple and thus not a Rex.
To match agains an int result or any 'T result:
let runQueryToQueryable (q:Expr<IQueryable<'T>>) =
match q with
| Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
let runQueryToType (q:Expr<'T>) =
match q with
| Application(Lambda(builder, Call(None, miRun, [builder2; Quote body])), queryObj) ->
query.Run(Expr.Cast<'T>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
Works like a charm.

F# dynamic option

I need to specify, that my member property will return something like dynamic? in C#. Is possible use dynamic data type in F#?
type Data =
| Text of string
| Number of string
| Date of string
with
member x.Value
with get() : dynamic option =
match x with
| Text(value) ->
if value.Length > 0 then Some(value) else None
| Number(value) ->
let (success, number) = Decimal.TryParse value
if (success) then Some(number) else None
| Date(value) ->
let (success, date) = DateTime.TryParse value
if (success) then Some(date) else None
This code cannot be compiled, because return type is determined as string option from Text case. Keyword dynamic is unknown in F#. Any ideas?
Try to make this datatype:
type ThreeWay = S of string | N of Decimal | D of DateTime
or, use the System.Object type:
open System
type Data =
| Text of string
| Number of string
| Date of string
with
member x.Value
with get() : Object option =
match x with
| Text(value) ->
if value.Length > 0 then Some(value :> Object) else None
| Number(value) ->
let (success, number) = Decimal.TryParse value
if (success) then Some(number :> Object) else None
| Date(value) ->
let (success, date) = DateTime.TryParse value
if (success) then Some(date :> Object) else None
To get the value:
let d = Number("123")
let v = d.Value
match v with
| Some(x) -> x :?> Decimal // <-- TYPE CAST HERE

Resources