/
Constants.Shared.fs
235 lines (183 loc) · 11.4 KB
/
Constants.Shared.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
namespace Grace.Shared
open Microsoft.FSharp.Reflection
open NodaTime.Serialization.SystemTextJson
open Polly
open Polly.Contrib.WaitAndRetry
open System
open System.IO
open System.Text.Encodings.Web
open System.Text.Json
open System.Text.Json.Serialization
open System.Text.RegularExpressions
open System.Collections.Generic
open System.Threading.Tasks
module Constants =
/// The universal serialization options for F#-specific data types in Grace.
///
/// See https://github.com/Tarmil/FSharp.SystemTextJson/blob/master/docs/Customizing.md for more information about these options.
let private jsonFSharpOptions =
JsonFSharpOptions.Default()
.WithAllowNullFields(true)
.WithUnionFieldsName("value")
.WithUnionTagNamingPolicy(JsonNamingPolicy.CamelCase)
.WithUnionTagCaseInsensitive(true)
.WithUnionEncoding(JsonUnionEncoding.ExternalTag |||
JsonUnionEncoding.UnwrapFieldlessTags |||
JsonUnionEncoding.UnwrapSingleFieldCases |||
JsonUnionEncoding.UnwrapSingleCaseUnions |||
JsonUnionEncoding.NamedFields)
.WithUnwrapOption(true)
/// The universal JSON serialization options for Grace.
let public JsonSerializerOptions = JsonSerializerOptions()
JsonSerializerOptions.AllowTrailingCommas <- true
JsonSerializerOptions.Converters.Add(JsonFSharpConverter(jsonFSharpOptions))
JsonSerializerOptions.ConfigureForNodaTime(NodaTime.DateTimeZoneProviders.Tzdb) |> ignore
JsonSerializerOptions.DefaultBufferSize <- 64 * 1024
JsonSerializerOptions.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingDefault // JsonSerializerOptions.IgnoreNullValues is deprecated. This is the new way to say it.
JsonSerializerOptions.NumberHandling <- JsonNumberHandling.AllowReadingFromString
JsonSerializerOptions.PropertyNameCaseInsensitive <- true // Case sensitivity is from the 1970's. We should let it go.
//JsonSerializerOptions.PropertyNamingPolicy <- JsonNamingPolicy.CamelCase
JsonSerializerOptions.ReadCommentHandling <- JsonCommentHandling.Skip
JsonSerializerOptions.ReferenceHandler <- ReferenceHandler.IgnoreCycles
JsonSerializerOptions.UnknownTypeHandling <- JsonUnknownTypeHandling.JsonElement
JsonSerializerOptions.WriteIndented <- true
JsonSerializerOptions.MaxDepth <- 16 // Default is 64, but if we exceed a depth of 16, we're probably doing something wrong.
/// Converts the full name of a discriminated union to a string. Example: ServerApiVersions.Latest -> "ServerApiVersions.Latest"
let discriminatedUnionFullName (value:'T) =
let discriminatedUnionType = typeof<'T>
let (case, _ ) = FSharpValue.GetUnionFields(value, discriminatedUnionType)
$"{discriminatedUnionType.Name}.{case.Name}"
/// Converts just the case name of a discriminated union to a string. Example: ServerApiVersions.Latest -> "Latest"
let discriminatedUnionCaseName (value:'T) =
let discriminatedUnionType = typeof<'T>
let (case, _ ) = FSharpValue.GetUnionFields(value, discriminatedUnionType)
$"{case.Name}"
/// The name of the Dapr service running Grace Server.
let GraceServerAppId = "grace-server"
/// The name of the Dapr service for Grace object storage.
let GraceObjectStorage = "graceobjectstorage"
/// The name of the Dapr service for Actor storage. This should be a document database.
let GraceActorStorage = "actorstorage"
/// The name of the Dapr service for Grace event pub/sub.
let GracePubSubService = "graceeventstream"
/// The name of the event topic to publish to.
let GraceEventStreamTopic = "graceeventstream"
/// The name of the Dapr service for retrieving application secrets.
let GraceSecretStoreName = "kubernetessecretstore"
/// The name of the directory that holds Grace information in a repository.
let GraceConfigDirectory = ".grace"
/// The name of the directory that holds locally-cached files in a repository.
let GraceObjectsDirectory = "objects"
/// The name of Grace's configuration file.
let GraceConfigFileName = "graceconfig.json"
/// The directory name of Grace's DirectoryVersion cache directory.
let GraceDirectoryVersionCacheName = "directoryVersions"
/// The name of the file that holds the file specifications to ignore.
let GraceIgnoreFileName = "graceignore.txt"
/// The name of the file that holds the current local index for Grace.
let GraceStatusFileName = "gracestatus.json.gz"
/// The name of the file that holds the current local index for Grace.
let GraceObjectCacheFile = "graceObjectCache.json.gz"
/// The default branch name for new repositories.
let InitialBranchName = "main"
/// The configuration version number used by this release of Grace.
let CurrentConfigurationVersion = "0.1"
/// The configuration version number used by this release of Grace.
let ServerApiVersionHeaderKey = "X-Api-Version"
/// A list of known Grace Server API version strings.
type ServerApiVersions =
| ``V2022-02-01``
| Latest
| Edge
override this.ToString() = discriminatedUnionFullName this
/// Environment variables used by Grace.
module EnvironmentVariables =
/// The environment variable that contains the Dapr server Uri. The Uri should not include a port number.
let DaprServerUri = "DAPR_SERVER_URI"
/// The environment variable that contains the application's port.
let GraceAppPort = "GRACE_APP_PORT"
/// The environment variable that contains the Dapr HTTP port.
let DaprHttpPort = "DAPR_HTTP_PORT"
/// The environment variable that contains the Dapr gRPC port.
let DaprGrpcPort = "DAPR_GRPC_PORT"
/// The environment variable that contains the Azure Cosmos DB Connection String.
let AzureCosmosDBConnectionString = "azurecosmosdbconnectionstring"
/// The environment variable that contains the Azure Storage Connection String.
let AzureStorageConnectionString = "azurestorageconnectionstring"
/// The environment variable that contains the Azure Storage Key.
let AzureStorageKey = "azurestoragekey"
/// The environment variable that contains the name of the CosmosDB database to use for Grace.
let CosmosDatabaseName = "cosmosdatabasename"
/// The environment variable that contains the name of the CosmosDB container to use for Grace.
let CosmosContainerName = "cosmoscontainername"
/// The default CacheControl header for object storage.
let BlobCacheControl = "public,max-age=86400,no-transform"
/// The expiration time for a Shared Access Signature token, in minutes.
let SharedAccessSignatureExpiration = 15.0
/// The path that indicates the root directory of the repository.
let RootDirectoryPath = "."
/// The key for the HttpContext metadata value that holds the CorrelationId for this transaction.
let CorrelationId = "correlationId"
/// The header name for a W3C trace.
let Traceparent = "traceparent"
/// The header name for W3C trace state.
let Tracestate = "tracestate"
/// The key for the HttpRequest and HttpResponse header that holds the CorrelationId for this transaction.
let CorrelationIdHeaderKey = "X-Correlation-Id"
/// <summary>
/// Validates that a string is a valid Grace object name.
///
/// Regex: ^[A-Za-z][A-Za-z0-9\-]{1,63}$
///
/// A valid object name in Grace has between 2 and 64 characters, has a letter for the first character ([A-Za-z]), and letters, numbers, or a dash (-) for the rest ([A-Za-z0-9\-_]{1,63}).
///
/// See https://regexper.com for a diagram.
/// </summary>
let GraceNameRegexText = "^[A-Za-z][A-Za-z0-9\-]{1,63}$"
/// <summary>
/// Validates that a string is a valid Grace object name.
///
/// Regex: ^[A-Za-z][A-Za-z0-9\-]{1,63}$
///
/// A valid object name in Grace has between 2 and 64 characters, has a letter for the first character ([A-Za-z]), and letters, numbers, or a dash (-) for the rest ([A-Za-z0-9\-_]{1,63}).
///
/// See https://regexper.com for a diagram.
/// </summary>
let GraceNameRegex = new Regex(GraceNameRegexText, RegexOptions.CultureInvariant ||| RegexOptions.Compiled, TimeSpan.FromSeconds(2.0))
// Note: The timeout value of 2s is a crazy big maximum time; matching against this should take less than 1ms.
/// Validates that a string is a full or partial valid SHA-256 hash value, between 2 and 64 hexadecimal characters.
///
/// Regex: ^[0-9a-fA-F]{2,64}$
let Sha256Regex = new Regex("^[0-9a-fA-F]{2,64}$", RegexOptions.CultureInvariant ||| RegexOptions.Compiled, TimeSpan.FromSeconds(1.0))
/// The backoff policy used by Grace for server requests.
let private backoffWithJitter = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay = (TimeSpan.FromSeconds(0.25)), retryCount = 7, fastFirst = false)
/// An exponential retry policy, with backoffs starting at 0.25s, and retrying 8 times.
let DefaultRetryPolicy = Policy.Handle<Exception>(fun ex -> ex.GetType() <> typeof<KeyNotFoundException>).WaitAndRetry(backoffWithJitter)
/// An exponential retry policy, with backoffs starting at 0.25s, and retrying 8 times.
let DefaultAsyncRetryPolicy = Policy.Handle<Exception>(fun ex -> ex.GetType() <> typeof<KeyNotFoundException>).WaitAndRetryAsync(backoffWithJitter)
let private fileCopyBackoff = Backoff.LinearBackoff(initialDelay = (TimeSpan.FromSeconds(1.0)), retryCount = 16, factor = 1.5, fastFirst = false)
/// A linear retry policy for copying files locally, with backoffs starting at 1s and retrying 16 times.
// This retry policy helps with large files. `grace watch` will see that the file is arriving, but if that file takes longer to be written than the next tick,
// we get an IOException when we try to compute the Sha256Hash and copy it to the object directory. This policy allows us to wait until the file is complete.
let DefaultFileCopyRetryPolicy = Policy.Handle<IOException>(fun ex -> ex.GetType() <> typeof<KeyNotFoundException>).WaitAndRetry(fileCopyBackoff)
/// Grace's global settings for Parallel.ForEach/ForEachAsync expressions; sets MaxDegreeofParallelism to maximize performance.
// I'm choosing a higher-than-usual number here because these parallel loops are used in code where most of the time is spent on network
// and disk traffic - and therefore Task<'T> - and we can run lots of them simultaneously.
let ParallelOptions = ParallelOptions(MaxDegreeOfParallelism = Environment.ProcessorCount * 4)
/// Default directory size magic value.
let InitialDirectorySize = uint64 Int64.MaxValue
/// The default root branch Id for a repository.
let DefaultParentBranchId = Guid("38EC9A98-00B0-4FA3-8CC5-ACFB04E445A7") // There's nothing special about this Guid. I just generated it one day.
/// The name of the inter-process communication file used by grace watch to share status with other invocations of Grace.
let IpcFileName = "graceWatchStatus.json"
/// The default expiration time for a cache entry.
let DefaultExpirationTime = TimeSpan.FromMinutes(5.0)
module Results =
let Ok = 0
let Exception = -1
let FileNotFound = -2
let ConfigurationFileNotFound = -3
let InvalidConfigurationFile = -4
let NotParsed = -98
let CommandNotFound = -99
let ThisShouldNeverHappen = -999