Skip to content

Commit 227a10d

Browse files
committed
add API gateway example
1 parent 1094b00 commit 227a10d

File tree

5 files changed

+281
-0
lines changed

5 files changed

+281
-0
lines changed
62.8 KB
Binary file not shown.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
4+
<PropertyGroup>
5+
<!-- Enable the restore command to run before builds -->
6+
<RestorePackages Condition=" '$(RestorePackages)' == '' ">true</RestorePackages>
7+
<PaketToolsPath>$(MSBuildThisFileDirectory)</PaketToolsPath>
8+
<PaketRootPath>$(MSBuildThisFileDirectory)..\</PaketRootPath>
9+
<PaketLockFilePath>$(PaketRootPath)paket.lock</PaketLockFilePath>
10+
<PaketRestoreCacheFile>$(PaketRootPath)paket-files\paket.restore.cached</PaketRestoreCacheFile>
11+
<MonoPath Condition="'$(MonoPath)' == '' And Exists('/Library/Frameworks/Mono.framework/Commands/mono')">/Library/Frameworks/Mono.framework/Commands/mono</MonoPath>
12+
<MonoPath Condition="'$(MonoPath)' == ''">mono</MonoPath>
13+
</PropertyGroup>
14+
15+
<PropertyGroup>
16+
<!-- Paket command -->
17+
<PaketExePath Condition=" '$(PaketExePath)' == '' AND Exists('$(PaketRootPath)paket.exe')">$(PaketRootPath)paket.exe</PaketExePath>
18+
<PaketExePath Condition=" '$(PaketExePath)' == '' ">$(PaketToolsPath)paket.exe</PaketExePath>
19+
<PaketCommand Condition=" '$(OS)' == 'Windows_NT'">"$(PaketExePath)"</PaketCommand>
20+
<PaketCommand Condition=" '$(OS)' != 'Windows_NT' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"</PaketCommand>
21+
</PropertyGroup>
22+
23+
<Choose> <!-- MyProject.fsproj.paket.references has the highest precedence -->
24+
<When Condition="Exists('$(MSBuildProjectFullPath).paket.references')">
25+
<PropertyGroup>
26+
<PaketReferences>$(MSBuildProjectFullPath).paket.references</PaketReferences>
27+
</PropertyGroup>
28+
</When> <!-- MyProject.paket.references -->
29+
<When Condition="Exists('$(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references')">
30+
<PropertyGroup>
31+
<PaketReferences>$(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references</PaketReferences>
32+
</PropertyGroup>
33+
</When> <!-- paket.references -->
34+
<When Condition="Exists('$(MSBuildProjectDirectory)\paket.references')">
35+
<PropertyGroup>
36+
<PaketReferences>$(MSBuildProjectDirectory)\paket.references</PaketReferences>
37+
</PropertyGroup>
38+
</When> <!-- Set to empty if a reference file isn't found matching one of the 3 format options -->
39+
<Otherwise>
40+
<PropertyGroup>
41+
<PaketReferences></PaketReferences>
42+
</PropertyGroup>
43+
</Otherwise>
44+
</Choose>
45+
46+
<PropertyGroup>
47+
<!-- Commands -->
48+
<RestoreCommand>$(PaketCommand) restore --references-file "$(PaketReferences)"</RestoreCommand>
49+
<!-- We need to ensure packages are restored prior to assembly resolve -->
50+
<BuildDependsOn Condition="$(RestorePackages) == 'true'">RestorePackages; $(BuildDependsOn);</BuildDependsOn>
51+
</PropertyGroup>
52+
<Target Name="RestorePackages">
53+
<PropertyGroup>
54+
<PaketRestoreRequired>true</PaketRestoreRequired>
55+
</PropertyGroup>
56+
57+
<PropertyGroup Condition="Exists('$(PaketRestoreCacheFile)') ">
58+
<PaketRestoreCachedHash>$([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))</PaketRestoreCachedHash>
59+
<PaketRestoreLockFileHash>$([System.IO.File]::ReadAllText('$(PaketLockFilePath)'))</PaketRestoreLockFileHash>
60+
<PaketRestoreRequired>true</PaketRestoreRequired>
61+
<PaketRestoreRequired Condition=" '$(PaketRestoreLockFileHash)' == '$(PaketRestoreCachedHash)' ">false</PaketRestoreRequired>
62+
<PaketRestoreRequired Condition=" '$(PaketRestoreLockFileHash)' == '' ">true</PaketRestoreRequired>
63+
</PropertyGroup>
64+
65+
<Exec Command="$(RestoreCommand)"
66+
IgnoreStandardErrorWarningFormat="true"
67+
WorkingDirectory="$(PaketRootPath)"
68+
ContinueOnError="false"
69+
Condition=" '$(PaketRestoreRequired)' == 'true' AND Exists('$(PaketReferences)') AND '$(PaketReferences)' != '' "
70+
/>
71+
</Target>
72+
</Project>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source https://www.nuget.org/api/v2
2+
framework: net461
3+
nuget FSharp.Data
4+
nuget Hopac
5+
nuget Http.Fs
6+
nuget System.Net.Http
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
RESTRICTION: == net461
2+
NUGET
3+
remote: https://www.nuget.org/api/v2
4+
FSharp.Core (4.3.4)
5+
FSharp.Data (2.4.5)
6+
Hopac (0.3.23)
7+
FSharp.Core (>= 3.1.2.5)
8+
Http.fs (5.0.1)
9+
FSharp.Core (>= 4.1.18)
10+
Hopac (>= 0.3.23)
11+
System.Net.Http (4.3.3)
12+
System.Security.Cryptography.X509Certificates (>= 4.3)
13+
System.Security.Cryptography.Algorithms (4.3.1)
14+
System.Security.Cryptography.Primitives (>= 4.3)
15+
System.Security.Cryptography.Encoding (4.3)
16+
System.Security.Cryptography.Primitives (4.3)
17+
System.Security.Cryptography.X509Certificates (4.3.2)
18+
System.Security.Cryptography.Algorithms (>= 4.3)
19+
System.Security.Cryptography.Encoding (>= 4.3)
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#r "packages/Hopac/lib/net45/Hopac.Core.dll"
2+
#r "packages/Hopac/lib/net45/Hopac.Platform.dll"
3+
#r "packages/Hopac/lib/net45/Hopac.dll"
4+
#r "packages/Hopac/lib/net45/Hopac.dll"
5+
#r "packages/FSharp.Data/lib/net45/FSharp.Data.dll"
6+
#r "packages/Http.fs/lib/net461/HttpFs.dll"
7+
#r "packages/System.Net.Http/lib/net46/System.Net.Http.dll"
8+
9+
open Hopac
10+
open FSharp.Data
11+
open HttpFs.Client
12+
open System
13+
14+
let httpGet url = job {
15+
let request =
16+
Request.createUrl Get url
17+
|> Request.setHeader (UserAgent "FsHopac")
18+
let! getResponseResult =
19+
getResponse request |> Job.catch
20+
match getResponseResult with
21+
| Choice1Of2 response ->
22+
match response.statusCode with
23+
| 200 ->
24+
let! body = Response.readBodyAsString response
25+
return Ok body
26+
| _ -> return Error (Exception("invalid response for " + url))
27+
| Choice2Of2 ex -> return Error ex
28+
}
29+
30+
31+
type GitHubUser = JsonProvider<"https://api.github.com/users/tamizhvendan">
32+
33+
type Profile = {
34+
Name : string
35+
AvatarUrl : string
36+
}
37+
38+
let profile (gitHubUser : GitHubUser.Root) = {
39+
Name = gitHubUser.Name
40+
AvatarUrl = gitHubUser.AvatarUrl
41+
}
42+
43+
44+
let host = "https://api.github.com"
45+
let userUrl = sprintf "%s/users/%s" host
46+
47+
let getGitHubProfile username = job {
48+
let! response = username |> userUrl |> httpGet
49+
let user =
50+
response
51+
|> Result.map GitHubUser.Parse
52+
|> Result.map profile
53+
return user
54+
}
55+
56+
57+
type GitHubRepos = JsonProvider<"https://api.github.com/users/tamizhvendan/repos">
58+
59+
type UserRepo = {
60+
Name : string
61+
StargazersCount : int
62+
}
63+
let userRepo (repo : GitHubRepos.Root) = {
64+
Name = repo.Name
65+
StargazersCount = repo.StargazersCount
66+
}
67+
68+
let isOwnRepo (repo : GitHubRepos.Root) = not repo.Fork
69+
70+
let topThreeUserRepos (repos : GitHubRepos.Root []) =
71+
let takeCount =
72+
let reposCount = Array.length repos
73+
if reposCount > 3 then 3 else reposCount
74+
repos
75+
|> Array.filter isOwnRepo
76+
|> Array.map userRepo
77+
|> Array.sortByDescending (fun repo -> repo.StargazersCount)
78+
|> Array.take takeCount
79+
80+
let reposUrl = sprintf "%s/users/%s/repos" host
81+
82+
let getTopThreeUserRepos username = job {
83+
let! response = username |> reposUrl |> httpGet
84+
let topThreeUserRepos =
85+
response
86+
|> Result.map GitHubRepos.Parse
87+
|> Result.map topThreeUserRepos
88+
return topThreeUserRepos
89+
}
90+
91+
let languagesUrl repoName userName =
92+
sprintf "%s/repos/%s/%s/languages" host userName repoName
93+
94+
let parseLanguagesJson languagesJson =
95+
languagesJson
96+
|> JsonValue.Parse
97+
|> JsonExtensions.Properties
98+
|> Array.map fst
99+
100+
let getUserRepoLanguages repoName username = job {
101+
let! response = languagesUrl repoName username |> httpGet
102+
let languages =
103+
response
104+
|> Result.map parseLanguagesJson
105+
return languages
106+
}
107+
108+
type UserRepoDto = {
109+
Name : string
110+
StargazersCount : int
111+
Languages : string []
112+
}
113+
let userRepoDto (userRepo : UserRepo) languagesResult =
114+
languagesResult
115+
|> Result.map (fun languages -> {
116+
Name = userRepo.Name
117+
StargazersCount = userRepo.StargazersCount
118+
Languages = languages
119+
})
120+
121+
type ProfileDto = {
122+
Name : string
123+
AvatarUrl : string
124+
TopThreeRepos : UserRepoDto list
125+
}
126+
127+
type ResultBuilder() =
128+
member __.Bind(r, binder) = Result.bind binder r
129+
member __.Return(value) = Ok value
130+
131+
let result = ResultBuilder()
132+
133+
let rec transform (results : Result<'a, Exception> list) : Result<'a list, Exception> =
134+
let values =
135+
results
136+
|> List.choose (function | Ok v -> Some v | _ -> None)
137+
if values.Length = results.Length then
138+
Ok values
139+
else
140+
let ex =
141+
results
142+
|> List.choose (function | Error ex -> Some ex | _ -> None)
143+
|> AggregateException
144+
Error (ex :> Exception)
145+
146+
147+
let profileDto (profileResult : Result<Profile,Exception>) userRepoDtosResults = result {
148+
let! profile = profileResult
149+
let! userRepoDtos =
150+
transform userRepoDtosResults
151+
let profileDto = {
152+
Name = profile.Name
153+
AvatarUrl = profile.AvatarUrl
154+
TopThreeRepos = userRepoDtos
155+
}
156+
return profileDto
157+
}
158+
159+
160+
let getUserRepoLanguagesJobs username (repos : UserRepo []) =
161+
repos
162+
|> Array.map (fun repo ->
163+
getUserRepoLanguages repo.Name username
164+
|> Job.map (userRepoDto repo))
165+
|> Job.conCollect
166+
|> Job.map (fun x -> x.ToArray() |> Array.toList)
167+
168+
open Hopac.Infixes
169+
let getProfileDto username = job {
170+
let! profile, topThreeUserRepos =
171+
getGitHubProfile username <*>
172+
getTopThreeUserRepos username
173+
let userRepoDtosJobResult =
174+
topThreeUserRepos
175+
|> Result.map (getUserRepoLanguagesJobs username)
176+
match userRepoDtosJobResult with
177+
| Ok userRepoDtosJob ->
178+
let! userRepoDtos = userRepoDtosJob
179+
return profileDto profile userRepoDtos
180+
| Error ex -> return Error ex
181+
}
182+
183+
getProfileDto "tamizhvendan" |> run
184+
getProfileDto "demystifyfp" |> run

0 commit comments

Comments
 (0)