Start generating parse methods #4

Merged
patrick merged 12 commits from visits into main 2023-12-28 21:08:09 +00:00
8 changed files with 259 additions and 58 deletions
Showing only changes of commit d7b078e8d6 - Show all commits

View File

@@ -17,8 +17,8 @@ type SessionsArgsFragment =
type SessionsArgs = type SessionsArgs =
{ {
Creds : Auth Creds : Auth
FromDate : DateTime FromDate : DateOnly
ToDate : DateTime ToDate : DateOnly
} }
static member Parse static member Parse
@@ -31,8 +31,8 @@ type SessionsArgs =
{ {
Creds = auth Creds = auth
FromDate = DateTime.Parse fromDate FromDate = DateOnly.Parse fromDate
ToDate = DateTime.Parse toDate ToDate = DateOnly.Parse toDate
} }
|> Ok |> Ok
@@ -42,7 +42,7 @@ module Sessions =
let run (args : SessionsArgs) = let run (args : SessionsArgs) =
task { task {
let! client = Dto.make args.Creds let! client = Dto.make args.Creds
let! activity = client.GetSessions args.FromDate args.ToDate let! activity = client.GetSessions (args.FromDate, args.ToDate)
System.Console.WriteLine (string<Sessions> activity) System.Console.WriteLine (string<Sessions> activity)
return 0 return 0

View File

@@ -6,6 +6,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PureGym.App", "PureGym.App\
EndProject EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PureGym.Test", "PureGym.Test\PureGym.Test.fsproj", "{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}" Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PureGym.Test", "PureGym.Test\PureGym.Test.fsproj", "{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}"
EndProject EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Myriad.Plugins", "..\WoofWare.Myriad\WoofWare.Myriad.Plugins\WoofWare.Myriad.Plugins.fsproj", "{ECA6B986-ED3A-4CC0-B37E-8E928C9DB9B3}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -24,5 +26,9 @@ Global
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU {F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Release|Any CPU.Build.0 = Release|Any CPU {F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Release|Any CPU.Build.0 = Release|Any CPU
{ECA6B986-ED3A-4CC0-B37E-8E928C9DB9B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECA6B986-ED3A-4CC0-B37E-8E928C9DB9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECA6B986-ED3A-4CC0-B37E-8E928C9DB9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECA6B986-ED3A-4CC0-B37E-8E928C9DB9B3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

27
PureGym/Api.fs Normal file
View File

@@ -0,0 +1,27 @@
namespace PureGym
open System
open System.Net.Http
open System.Threading.Tasks
/// Methods for interacting with the PureGym REST API.
[<RequireQualifiedAccess>]
module Dto =
/// Create a REST client, authenticated as the specified user.
let make (auth : Auth) : IPureGymApi Task =
task {
let! token =
match auth with
| Auth.Token t -> Task.FromResult<_> t
| Auth.User cred -> AuthToken.get cred
let client = new HttpClient ()
client.BaseAddress <- Uri "https://capi.puregym.com/api"
client.DefaultRequestHeaders.Authorization <-
Headers.AuthenticationHeaderValue ("Bearer", token.AccessToken)
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
return PureGymApi.make client
}

View File

@@ -2,57 +2,38 @@ namespace PureGym
open System open System
open System.Net.Http open System.Net.Http
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open RestEase open RestEase
/// The PureGym REST API. You probably want to instantiate one of these with `Api.make`. /// The PureGym REST API. You probably want to instantiate one of these with `Api.make`.
[<WoofWare.Myriad.Plugins.HttpClient>]
[<Header("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")>] [<Header("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")>]
type IPureGymApi = type IPureGymApi =
/// Get the complete list of all gyms known to PureGym. /// Get the complete list of all gyms known to PureGym.
[<Get "v1/gyms/">] [<Get "v1/gyms/">]
abstract GetGyms : unit -> Task<Gym list> abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
/// Get information about the PureGym human whose credentials this client is authenticated with. /// Get information about the PureGym human whose credentials this client is authenticated with.
[<Get "v1/member">] [<Get "v1/member">]
abstract GetMember : unit -> Task<Member> abstract GetMember : ?ct : CancellationToken -> Task<Member>
/// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`. /// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`.
[<Get "v1/gyms/{gym_id}/attendance">] [<Get "v1/gyms/{gym_id}/attendance">]
abstract GetGymAttendance : [<Path "gym_id">] gymId : int -> Task<GymAttendance> abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
/// Get information about a specific gym. /// Get information about a specific gym.
[<Get "v1/gyms/{gym_id}">] [<Get "v1/gyms/{gym_id}">]
abstract GetGym : [<Path "gym_id">] gymId : int -> Task<Gym> abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
/// Get information about the activities logged against the currently authenticated PureGym human. /// Get information about the activities logged against the currently authenticated PureGym human.
[<Get "v1/member/activity">] [<Get "v1/member/activity">]
abstract GetMemberActivity : unit -> Task<MemberActivityDto> abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
/// Get information about the individual visits to all PureGyms the currently-authenticated PureGym human has made. /// Get information about the individual visits to all PureGyms the currently-authenticated PureGym human has made.
[<Get "v2/gymSessions/member">] [<Get "v2/gymSessions/member">]
abstract GetSessions : [<Query>] fromDate : DateTime -> [<Query>] toDate : DateTime -> Task<Sessions> abstract GetSessions :
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
// [<Get "v1/member/activity/history">] // [<Get "v1/member/activity/history">]
// abstract GetMemberActivityAll : unit -> Task<string> // abstract GetMemberActivityAll : ?ct: CancellationToken -> Task<string>
/// Methods for interacting with the PureGym REST API.
[<RequireQualifiedAccess>]
module Dto =
/// Create a REST client, authenticated as the specified user.
let make (auth : Auth) : IPureGymApi Task =
task {
let! token =
match auth with
| Auth.Token t -> Task.FromResult<_> t
| Auth.User cred -> AuthToken.get cred
let client = new HttpClient ()
client.BaseAddress <- Uri "https://capi.puregym.com/api"
client.DefaultRequestHeaders.Authorization <-
Headers.AuthenticationHeaderValue ("Bearer", token.AccessToken)
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
return RestClient.For<IPureGymApi> client
}

View File

@@ -274,10 +274,13 @@ type SessionsAggregate =
{ {
/// Number of gym "activities" within some query-defined time period; presumably this is like classes? /// Number of gym "activities" within some query-defined time period; presumably this is like classes?
/// It's always 0 for me. /// It's always 0 for me.
[<JsonPropertyName "Activities">]
Activities : int Activities : int
/// Number of visits to the gym within some query-defined time period. /// Number of visits to the gym within some query-defined time period.
[<JsonPropertyName "Visits">]
Visits : int Visits : int
/// In minutes: total time spent in gym during the query-defined time period. /// In minutes: total time spent in gym during the query-defined time period.
[<JsonPropertyName "Duration">]
Duration : int Duration : int
} }
@@ -287,10 +290,13 @@ type VisitGym =
{ {
// Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me // Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me
/// The PureGym ID of this gym, e.g. 19 /// The PureGym ID of this gym, e.g. 19
[<JsonPropertyName "Id">]
Id : int Id : int
/// E.g. "London Oval", the canonical name of this gym /// E.g. "London Oval", the canonical name of this gym
[<JsonPropertyName "Name">]
Name : string Name : string
/// For some reason this always seems to be "Blocked" /// For some reason this always seems to be "Blocked"
[<JsonPropertyName "Status">]
Status : string Status : string
} }
@@ -300,12 +306,16 @@ type Visit =
{ {
// Omitted Name because it always was null for me // Omitted Name because it always was null for me
/// Whether the Duration field is estimated. /// Whether the Duration field is estimated.
[<JsonPropertyName "IsDurationEstimated">]
IsDurationEstimated : bool IsDurationEstimated : bool
/// When the visit began. /// When the visit began.
[<JsonPropertyName "StartTime">]
StartTime : DateTime StartTime : DateTime
/// In minutes. /// In minutes.
[<JsonPropertyName "Duration">]
Duration : int Duration : int
/// Which gym was visited /// Which gym was visited
[<JsonPropertyName "Gym">]
Gym : VisitGym Gym : VisitGym
} }
@@ -319,8 +329,10 @@ type Visit =
type SessionsSummary = type SessionsSummary =
{ {
/// Aggregate stats for gym visits within the query-dependent time period. /// Aggregate stats for gym visits within the query-dependent time period.
[<JsonPropertyName "Total">]
Total : SessionsAggregate Total : SessionsAggregate
/// Aggregate stats for gym visits "this week", whatever that means to PureGym. /// Aggregate stats for gym visits "this week", whatever that means to PureGym.
[<JsonPropertyName "ThisWeek">]
ThisWeek : SessionsAggregate ThisWeek : SessionsAggregate
} }
@@ -330,7 +342,9 @@ type SessionsSummary =
[<WoofWare.Myriad.Plugins.JsonParse>] [<WoofWare.Myriad.Plugins.JsonParse>]
type Sessions = type Sessions =
{ {
[<JsonPropertyName "Summary">]
Summary : SessionsSummary Summary : SessionsSummary
[<JsonPropertyName "Visits">]
Visits : Visit list Visits : Visit list
} }

169
PureGym/GeneratedClient.fs Normal file
View File

@@ -0,0 +1,169 @@
//------------------------------------------------------------------------------
// This code was generated by myriad.
// Changes to this file will be lost when the code is regenerated.
//------------------------------------------------------------------------------
namespace PureGym
open System
open System.Net.Http
open System.Threading
open System.Threading.Tasks
open RestEase
/// Module for constructing a REST client.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module PureGymApi =
/// Create a REST client.
let make (client : System.Net.Http.HttpClient) : IPureGymApi =
{ new IPureGymApi with
member _.GetGyms (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = System.Uri (client.BaseAddress.ToString () + "/v1/gyms/")
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetMember (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = System.Uri (client.BaseAddress.ToString () + "/v1/member")
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return Member.jsonParse node
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetGymAttendance (gymId : int, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri =
System.Uri (
client.BaseAddress.ToString ()
+ "/v1/gyms/{gym_id}/attendance".Replace ("{gym_id}", gymId.ToString ())
)
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return GymAttendance.jsonParse node
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetGym (gymId : int, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri =
System.Uri (
client.BaseAddress.ToString ()
+ "/v1/gyms/{gym_id}".Replace ("{gym_id}", gymId.ToString ())
)
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return Gym.jsonParse node
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetMemberActivity (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = System.Uri (client.BaseAddress.ToString () + "/v1/member/activity")
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return MemberActivityDto.jsonParse node
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member _.GetSessions (fromDate : DateOnly, toDate : DateOnly, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri =
System.Uri (
client.BaseAddress.ToString ()
+ ("/v2/gymSessions/member"
+ "?fromDate="
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)
+ "&toDate="
+ ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode))
)
)
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! node =
System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct)
|> Async.AwaitTask
return Sessions.jsonParse node
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}

View File

@@ -266,9 +266,9 @@ namespace PureGym
module SessionsAggregate = module SessionsAggregate =
/// Parse from a JSON node. /// Parse from a JSON node.
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsAggregate = let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsAggregate =
let Duration = node.["duration"].AsValue().GetValue<int> () let Duration = node.["Duration"].AsValue().GetValue<int> ()
let Visits = node.["visits"].AsValue().GetValue<int> () let Visits = node.["Visits"].AsValue().GetValue<int> ()
let Activities = node.["activities"].AsValue().GetValue<int> () let Activities = node.["Activities"].AsValue().GetValue<int> ()
{ {
Activities = Activities Activities = Activities
@@ -283,9 +283,9 @@ namespace PureGym
module VisitGym = module VisitGym =
/// Parse from a JSON node. /// Parse from a JSON node.
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : VisitGym = let jsonParse (node : System.Text.Json.Nodes.JsonNode) : VisitGym =
let Status = node.["status"].AsValue().GetValue<string> () let Status = node.["Status"].AsValue().GetValue<string> ()
let Name = node.["name"].AsValue().GetValue<string> () let Name = node.["Name"].AsValue().GetValue<string> ()
let Id = node.["id"].AsValue().GetValue<int> () let Id = node.["Id"].AsValue().GetValue<int> ()
{ {
Id = Id Id = Id
@@ -300,13 +300,13 @@ namespace PureGym
module Visit = module Visit =
/// Parse from a JSON node. /// Parse from a JSON node.
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Visit = let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Visit =
let Gym = VisitGym.jsonParse node.["gym"] let Gym = VisitGym.jsonParse node.["Gym"]
let Duration = node.["duration"].AsValue().GetValue<int> () let Duration = node.["Duration"].AsValue().GetValue<int> ()
let StartTime = let StartTime =
node.["startTime"].AsValue().GetValue<string> () |> System.DateTime.Parse node.["StartTime"].AsValue().GetValue<string> () |> System.DateTime.Parse
let IsDurationEstimated = node.["isDurationEstimated"].AsValue().GetValue<bool> () let IsDurationEstimated = node.["IsDurationEstimated"].AsValue().GetValue<bool> ()
{ {
IsDurationEstimated = IsDurationEstimated IsDurationEstimated = IsDurationEstimated
@@ -322,8 +322,8 @@ namespace PureGym
module SessionsSummary = module SessionsSummary =
/// Parse from a JSON node. /// Parse from a JSON node.
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsSummary = let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsSummary =
let ThisWeek = SessionsAggregate.jsonParse node.["thisWeek"] let ThisWeek = SessionsAggregate.jsonParse node.["ThisWeek"]
let Total = SessionsAggregate.jsonParse node.["total"] let Total = SessionsAggregate.jsonParse node.["Total"]
{ {
Total = Total Total = Total
@@ -338,11 +338,11 @@ module Sessions =
/// Parse from a JSON node. /// Parse from a JSON node.
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Sessions = let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Sessions =
let Visits = let Visits =
node.["visits"].AsArray () node.["Visits"].AsArray ()
|> Seq.map (fun elt -> Visit.jsonParse elt) |> Seq.map (fun elt -> Visit.jsonParse elt)
|> List.ofSeq |> List.ofSeq
let Summary = SessionsSummary.jsonParse node.["summary"] let Summary = SessionsSummary.jsonParse node.["Summary"]
{ {
Summary = Summary Summary = Summary

View File

@@ -5,23 +5,27 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarnOn>FS3559</WarnOn> <WarnOn>FS3559</WarnOn>
<WoofWareMyriadPluginVersion>1.0.4</WoofWareMyriadPluginVersion> <WoofWareMyriadPluginVersion>1.1.1</WoofWareMyriadPluginVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="String.fs" /> <Compile Include="String.fs" />
<Compile Include="Auth.fs" /> <Compile Include="Auth.fs" />
<Compile Include="Dto.fs" /> <Compile Include="Dto.fs" />
<Compile Include="GeneratedDto.fs"> <!--1--> <Compile Include="GeneratedDto.fs"> <!--1-->
<MyriadFile>Dto.fs</MyriadFile> <!--2--> <MyriadFile>Dto.fs</MyriadFile> <!--2-->
</Compile> </Compile>
<Compile Include="Client.fs" /> <Compile Include="Client.fs" />
<Compile Include="GymSelector.fs" /> <Compile Include="GeneratedClient.fs">
<EmbeddedResource Include="SurfaceBaseline.txt" /> <MyriadFile>Client.fs</MyriadFile> <!--2-->
<EmbeddedResource Include="version.json" /> </Compile>
<Compile Include="Api.fs" />
<Compile Include="GymSelector.fs" />
<EmbeddedResource Include="SurfaceBaseline.txt" />
<EmbeddedResource Include="version.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" /> <MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
</ItemGroup> </ItemGroup>
@@ -29,7 +33,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="RestEase" Version="1.6.4" /> <PackageReference Include="RestEase" Version="1.6.4" />
<PackageReference Update="FSharp.Core" Version="6.0.1" /> <PackageReference Update="FSharp.Core" Version="6.0.1" />
<PackageReference Include="System.Text.Json" Version="7.0.3" /> <PackageReference Include="System.Text.Json" Version="8.0.0" />
<PackageReference Include="Fastenshtein" Version="1.0.0.8" /> <PackageReference Include="Fastenshtein" Version="1.0.0.8" />
<PackageReference Include="Myriad.Core" Version="0.8.3" /> <PackageReference Include="Myriad.Core" Version="0.8.3" />
<PackageReference Include="Myriad.Sdk" Version="0.8.3" /> <PackageReference Include="Myriad.Sdk" Version="0.8.3" />