summaryrefslogtreecommitdiff
path: root/src/SomhairlesDream.Shared/Artifact.fs
blob: d1cb4f0ff81c28d93ece5c7b0e4d4fd2c0c88e3f (plain)
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
namespace SomhairlesDream.Shared

open System
#if !FABLE_COMPILER
open System.Text.Json
open System.Text.Json.Nodes
#endif

[<CLIMutable>]
type RunIds =
    { ProjectId: string
      RunId: string
      BaseId: string
      TargetId: string }

module Ids =
    open System.Text.RegularExpressions

    let private resourcePattern = Regex("^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$", RegexOptions.CultureInvariant)
    let private objectPattern = Regex("^[a-z0-9]+(?:[._-][a-z0-9]+)*$", RegexOptions.CultureInvariant)

    let validateResourceId value =
        if isNull value || not (resourcePattern.IsMatch(value)) then
            Error "resource id must contain only lower-case letters, digits, and hyphens"
        else
            Ok ()

    let validateObjectId value =
        if isNull value || not (objectPattern.IsMatch(value)) then
            Error "object id must use the dotted lower-case namespace"
        else
            Ok ()

[<CLIMutable>]
type RunStart =
    { Ids: RunIds
      At: DateTimeOffset
      TotalSteps: int }

[<CLIMutable>]
type RunHeartbeat =
    { Ids: RunIds
      At: DateTimeOffset
      Message: string }

[<CLIMutable>]
type RunCheckpoint =
    { Ids: RunIds
      At: DateTimeOffset
      StepId: string
      StepIndex: int
      TotalSteps: int
      Message: string
      ArtifactPath: string }

[<CLIMutable>]
type RunComplete =
    { Ids: RunIds
      At: DateTimeOffset
      ManifestPath: string }

[<CLIMutable>]
type RunFail =
    { Ids: RunIds
      At: DateTimeOffset
      StepId: string option
      Message: string }

type RunEvent =
    | Start of RunStart
    | Heartbeat of RunHeartbeat
    | Checkpoint of RunCheckpoint
    | Complete of RunComplete
    | Fail of RunFail

[<CLIMutable>]
type RunEventWire =
    { Kind: string
      At: DateTimeOffset
      ProjectId: string
      RunId: string
      BaseId: string
      TargetId: string
      TotalSteps: int option
      StepId: string option
      StepIndex: int option
      Message: string option
      ArtifactPath: string option
      ManifestPath: string option }

module RunEvent =
    let private ids (value: RunIds) =
        value.ProjectId, value.RunId, value.BaseId, value.TargetId

    let toWire event =
        let kind, eventIds, at, totalSteps, stepId, stepIndex, message, artifactPath, manifestPath =
            match event with
            | Start value ->
                "start", value.Ids, value.At, Some value.TotalSteps, None, None, None, None, None
            | Heartbeat value ->
                "heartbeat", value.Ids, value.At, None, None, None, Some value.Message, None, None
            | Checkpoint value ->
                "checkpoint",
                value.Ids,
                value.At,
                Some value.TotalSteps,
                Some value.StepId,
                Some value.StepIndex,
                Some value.Message,
                Some value.ArtifactPath,
                None
            | Complete value ->
                "complete", value.Ids, value.At, None, None, None, None, None, Some value.ManifestPath
            | Fail value ->
                "fail", value.Ids, value.At, None, value.StepId, None, Some value.Message, None, None

        let projectId, runId, baseId, targetId = ids eventIds

        { Kind = kind
          At = at
          ProjectId = projectId
          RunId = runId
          BaseId = baseId
          TargetId = targetId
          TotalSteps = totalSteps
          StepId = stepId
          StepIndex = stepIndex
          Message = message
          ArtifactPath = artifactPath
          ManifestPath = manifestPath }

#if !FABLE_COMPILER
    let private addOptional (json: JsonObject) (name: string) value create =
        match value with
        | Some item -> json[name] <- create item
        | None -> ()

    let private jsonOptions () =
        let options = JsonSerializerOptions()
        options.PropertyNamingPolicy <- JsonNamingPolicy.CamelCase
        options.WriteIndented <- false
        options

    let toJson event =
        let wire = toWire event
        let json = JsonObject()
        json["kind"] <- JsonValue.Create(wire.Kind)
        json["at"] <- JsonValue.Create(wire.At)
        json["projectId"] <- JsonValue.Create(wire.ProjectId)
        json["runId"] <- JsonValue.Create(wire.RunId)
        json["baseId"] <- JsonValue.Create(wire.BaseId)
        json["targetId"] <- JsonValue.Create(wire.TargetId)
        addOptional json "totalSteps" wire.TotalSteps (fun value -> JsonValue.Create(value))
        addOptional json "stepId" wire.StepId (fun value -> JsonValue.Create(value))
        addOptional json "stepIndex" wire.StepIndex (fun value -> JsonValue.Create(value))
        addOptional json "message" wire.Message (fun value -> JsonValue.Create(value))
        addOptional json "artifactPath" wire.ArtifactPath (fun value -> JsonValue.Create(value))
        addOptional json "manifestPath" wire.ManifestPath (fun value -> JsonValue.Create(value))
        json.ToJsonString(jsonOptions())
#endif

[<CLIMutable>]
type ArtifactChange =
    { ObjectId: string
      Kind: string
      Summary: string }

[<CLIMutable>]
type RenderArtifact =
    { ArtifactPath: string
      Sha256: string
      Bytes: int64
      ExportedAt: DateTimeOffset }

[<CLIMutable>]
type StepArtifact =
    { StepId: string
      StepIndex: int
      ArtifactPath: string
      LogPath: string
      Sha256: string
      Bytes: int64
      ExportedAt: DateTimeOffset
      ObjectIds: string array
      VerifiedObjectIds: string array
      Changes: ArtifactChange array
      Render: RenderArtifact option }

[<CLIMutable>]
type ArtifactManifest =
    { SchemaVersion: int
      ProjectId: string
      RunId: string
      BaseId: string
      TargetId: string
      Status: string
      StartedAt: DateTimeOffset
      CompletedAt: DateTimeOffset
      BlenderVersion: string
      Steps: StepArtifact array }

[<CLIMutable>]
type RunSnapshot =
    { ProjectId: string
      RunId: string
      BaseId: string
      TargetId: string
      Status: string
      CurrentStepId: string option
      CurrentStepIndex: int
      TotalSteps: int
      LastHeartbeatAt: DateTimeOffset option
      UpdatedAt: DateTimeOffset
      Message: string
      ManifestPath: string option
      ManifestUrl: string option
      Error: string option
      IsStale: bool }