diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 847e9cfe..8d5cb22f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,5 +18,4 @@ jobs: with: python-version: 3.x - run: pip install mkdocs-material - - run: go run ./cmd/core dev docgen - run: mkdocs gh-deploy --force diff --git a/Taskfile.yml b/Taskfile.yml index 693b3da3..f35896f3 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -42,13 +42,3 @@ tasks: cmds: - task: cov - go tool cover -html=coverage.txt - - sync: - desc: "Updates the public API Go files to match the exported interface of the modules." - cmds: - - task: cli:sync - - test-gen: - desc: "Generates tests for the public API." - cmds: - - task: cli:test-gen diff --git a/docs/services/e.md b/docs/services/e.md index ef7542de..6db7114b 100644 --- a/docs/services/e.md +++ b/docs/services/e.md @@ -19,155 +19,36 @@ The key features of this package are: - Consistent error handling: Encourages a uniform approach to error handling across the entire codebase. - - - - ## Types ### `type Error` + +`Error` represents a standardized error with operational context. + ```go -type Error 0 *ast.StructType { - 1 . Struct: - - 2 . Fields: *ast.FieldList { - 3 . . Opening: - - 4 . . List: []*ast.Field (len = 3) { - 5 . . . 0: *ast.Field { - 6 . . . . Doc: *ast.CommentGroup { - 7 . . . . . List: []*ast.Comment (len = 1) { - 8 . . . . . . 0: *ast.Comment { - 9 . . . . . . . Slash: - - 10 . . . . . . . Text: "// Op is the operation being performed, e.g., \"config.Load\"." - 11 . . . . . . } - 12 . . . . . } - 13 . . . . } - 14 . . . . Names: []*ast.Ident (len = 1) { - 15 . . . . . 0: *ast.Ident { - 16 . . . . . . NamePos: - - 17 . . . . . . Name: "Op" - 18 . . . . . . Obj: *ast.Object { - 19 . . . . . . . Kind: var - 20 . . . . . . . Name: "Op" - 21 . . . . . . . Decl: *(obj @ 5) - 22 . . . . . . . Data: nil - 23 . . . . . . . Type: nil - 24 . . . . . . } - 25 . . . . . } - 26 . . . . } - 27 . . . . Type: *ast.Ident { - 28 . . . . . NamePos: - - 29 . . . . . Name: "string" - 30 . . . . . Obj: nil - 31 . . . . } - 32 . . . . Tag: nil - 33 . . . . Comment: nil - 34 . . . } - 35 . . . 1: *ast.Field { - 36 . . . . Doc: *ast.CommentGroup { - 37 . . . . . List: []*ast.Comment (len = 1) { - 38 . . . . . . 0: *ast.Comment { - 39 . . . . . . . Slash: - - 40 . . . . . . . Text: "// Msg is a human-readable message explaining the error." - 41 . . . . . . } - 42 . . . . . } - 43 . . . . } - 44 . . . . Names: []*ast.Ident (len = 1) { - 45 . . . . . 0: *ast.Ident { - 46 . . . . . . NamePos: - - 47 . . . . . . Name: "Msg" - 48 . . . . . . Obj: *ast.Object { - 49 . . . . . . . Kind: var - 50 . . . . . . . Name: "Msg" - 51 . . . . . . . Decl: *(obj @ 35) - 52 . . . . . . . Data: nil - 53 . . . . . . . Type: nil - 54 . . . . . . } - 55 . . . . . } - 56 . . . . } - 57 . . . . Type: *ast.Ident { - 58 . . . . . NamePos: - - 59 . . . . . Name: "string" - 60 . . . . . Obj: nil - 61 . . . . } - 62 . . . . Tag: nil - 63 . . . . Comment: nil - 64 . . . } - 65 . . . 2: *ast.Field { - 66 . . . . Doc: *ast.CommentGroup { - 67 . . . . . List: []*ast.Comment (len = 1) { - 68 . . . . . . 0: *ast.Comment { - 69 . . . . . . . Slash: - - 70 . . . . . . . Text: "// Err is the underlying error that was wrapped." - 71 . . . . . . } - 72 . . . . . } - 73 . . . . } - 74 . . . . Names: []*ast.Ident (len = 1) { - 75 . . . . . 0: *ast.Ident { - 76 . . . . . . NamePos: - - 77 . . . . . . Name: "Err" - 78 . . . . . . Obj: *ast.Object { - 79 . . . . . . . Kind: var - 80 . . . . . . . Name: "Err" - 81 . . . . . . . Decl: *(obj @ 65) - 82 . . . . . . . Data: nil - 83 . . . . . . . Type: nil - 84 . . . . . . } - 85 . . . . . } - 86 . . . . } - 87 . . . . Type: *ast.Ident { - 88 . . . . . NamePos: - - 89 . . . . . Name: "error" - 90 . . . . . Obj: nil - 91 . . . . } - 92 . . . . Tag: nil - 93 . . . . Comment: nil - 94 . . . } - 95 . . } - 96 . . Closing: - - 97 . } - 98 . Incomplete: false - 99 } - +type Error struct { + // Op is the operation being performed, e.g., "config.Load". + Op string + // Msg is a human-readable message explaining the error. + Msg string + // Err is the underlying error that was wrapped. + Err error +} ``` -Error represents a standardized error with operational context. - - #### Methods -- `Error() 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -`: Error returns the string representation of the error. - -- `Unwrap() 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Unwrap provides compatibility for Go's errors.Is and errors.As functions. - - - - +- `Error() string`: Error returns the string representation of the error. +- `Unwrap() error`: Unwrap provides compatibility for Go's errors.Is and errors.As functions. ## Functions -- `E(op, msg 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, err 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: E is a helper function to create a new Error. This is the primary way to create errors that will be consumed by the system. For example: return e.E("config.Load", "failed to load config file", err) The 'op' parameter should be in the format of 'package.function' or 'service.method'. The 'msg' parameter should be a human-readable message that can be displayed to the user. The 'err' parameter is the underlying error that is being wrapped. +- `E(op, msg string, err error) error`: E is a helper function to create a new Error. + +This is the primary way to create errors that will be consumed by the system. For example: + +```go +return e.E("config.Load", "failed to load config file", err) +``` + +The `op` parameter should be in the format of `package.function` or `service.method`. The `msg` parameter should be a human-readable message that can be displayed to the user. The `err` parameter is the underlying error that is being wrapped. diff --git a/docs/services/io.md b/docs/services/io.md index 7179ea07..8cb4a538 100644 --- a/docs/services/io.md +++ b/docs/services/io.md @@ -3,795 +3,62 @@ title: io --- # Service: `io` - - - - +The `io` service provides a standardized interface for interacting with different storage backends, such as the local disk, S3, or SFTP. ## Types ### `type Medium` + +`Medium` defines the standard interface for a storage backend. + ```go -type Medium 0 *ast.InterfaceType { - 1 . Interface: - - 2 . Methods: *ast.FieldList { - 3 . . Opening: - - 4 . . List: []*ast.Field (len = 6) { - 5 . . . 0: *ast.Field { - 6 . . . . Doc: *ast.CommentGroup { - 7 . . . . . List: []*ast.Comment (len = 1) { - 8 . . . . . . 0: *ast.Comment { - 9 . . . . . . . Slash: - - 10 . . . . . . . Text: "// Read retrieves the content of a file as a string." - 11 . . . . . . } - 12 . . . . . } - 13 . . . . } - 14 . . . . Names: []*ast.Ident (len = 1) { - 15 . . . . . 0: *ast.Ident { - 16 . . . . . . NamePos: - - 17 . . . . . . Name: "Read" - 18 . . . . . . Obj: *ast.Object { - 19 . . . . . . . Kind: func - 20 . . . . . . . Name: "Read" - 21 . . . . . . . Decl: *(obj @ 5) - 22 . . . . . . . Data: nil - 23 . . . . . . . Type: nil - 24 . . . . . . } - 25 . . . . . } - 26 . . . . } - 27 . . . . Type: *ast.FuncType { - 28 . . . . . Func: - - 29 . . . . . TypeParams: nil - 30 . . . . . Params: *ast.FieldList { - 31 . . . . . . Opening: - - 32 . . . . . . List: []*ast.Field (len = 1) { - 33 . . . . . . . 0: *ast.Field { - 34 . . . . . . . . Doc: nil - 35 . . . . . . . . Names: []*ast.Ident (len = 1) { - 36 . . . . . . . . . 0: *ast.Ident { - 37 . . . . . . . . . . NamePos: - - 38 . . . . . . . . . . Name: "path" - 39 . . . . . . . . . . Obj: *ast.Object { - 40 . . . . . . . . . . . Kind: var - 41 . . . . . . . . . . . Name: "path" - 42 . . . . . . . . . . . Decl: *(obj @ 33) - 43 . . . . . . . . . . . Data: nil - 44 . . . . . . . . . . . Type: nil - 45 . . . . . . . . . . } - 46 . . . . . . . . . } - 47 . . . . . . . . } - 48 . . . . . . . . Type: *ast.Ident { - 49 . . . . . . . . . NamePos: - - 50 . . . . . . . . . Name: "string" - 51 . . . . . . . . . Obj: nil - 52 . . . . . . . . } - 53 . . . . . . . . Tag: nil - 54 . . . . . . . . Comment: nil - 55 . . . . . . . } - 56 . . . . . . } - 57 . . . . . . Closing: - - 58 . . . . . } - 59 . . . . . Results: *ast.FieldList { - 60 . . . . . . Opening: - - 61 . . . . . . List: []*ast.Field (len = 2) { - 62 . . . . . . . 0: *ast.Field { - 63 . . . . . . . . Doc: nil - 64 . . . . . . . . Names: nil - 65 . . . . . . . . Type: *ast.Ident { - 66 . . . . . . . . . NamePos: - - 67 . . . . . . . . . Name: "string" - 68 . . . . . . . . . Obj: nil - 69 . . . . . . . . } - 70 . . . . . . . . Tag: nil - 71 . . . . . . . . Comment: nil - 72 . . . . . . . } - 73 . . . . . . . 1: *ast.Field { - 74 . . . . . . . . Doc: nil - 75 . . . . . . . . Names: nil - 76 . . . . . . . . Type: *ast.Ident { - 77 . . . . . . . . . NamePos: - - 78 . . . . . . . . . Name: "error" - 79 . . . . . . . . . Obj: nil - 80 . . . . . . . . } - 81 . . . . . . . . Tag: nil - 82 . . . . . . . . Comment: nil - 83 . . . . . . . } - 84 . . . . . . } - 85 . . . . . . Closing: - - 86 . . . . . } - 87 . . . . } - 88 . . . . Tag: nil - 89 . . . . Comment: nil - 90 . . . } - 91 . . . 1: *ast.Field { - 92 . . . . Doc: *ast.CommentGroup { - 93 . . . . . List: []*ast.Comment (len = 1) { - 94 . . . . . . 0: *ast.Comment { - 95 . . . . . . . Slash: - - 96 . . . . . . . Text: "// Write saves the given content to a file, overwriting it if it exists." - 97 . . . . . . } - 98 . . . . . } - 99 . . . . } - 100 . . . . Names: []*ast.Ident (len = 1) { - 101 . . . . . 0: *ast.Ident { - 102 . . . . . . NamePos: - - 103 . . . . . . Name: "Write" - 104 . . . . . . Obj: *ast.Object { - 105 . . . . . . . Kind: func - 106 . . . . . . . Name: "Write" - 107 . . . . . . . Decl: *(obj @ 91) - 108 . . . . . . . Data: nil - 109 . . . . . . . Type: nil - 110 . . . . . . } - 111 . . . . . } - 112 . . . . } - 113 . . . . Type: *ast.FuncType { - 114 . . . . . Func: - - 115 . . . . . TypeParams: nil - 116 . . . . . Params: *ast.FieldList { - 117 . . . . . . Opening: - - 118 . . . . . . List: []*ast.Field (len = 1) { - 119 . . . . . . . 0: *ast.Field { - 120 . . . . . . . . Doc: nil - 121 . . . . . . . . Names: []*ast.Ident (len = 2) { - 122 . . . . . . . . . 0: *ast.Ident { - 123 . . . . . . . . . . NamePos: - - 124 . . . . . . . . . . Name: "path" - 125 . . . . . . . . . . Obj: *ast.Object { - 126 . . . . . . . . . . . Kind: var - 127 . . . . . . . . . . . Name: "path" - 128 . . . . . . . . . . . Decl: *(obj @ 119) - 129 . . . . . . . . . . . Data: nil - 130 . . . . . . . . . . . Type: nil - 131 . . . . . . . . . . } - 132 . . . . . . . . . } - 133 . . . . . . . . . 1: *ast.Ident { - 134 . . . . . . . . . . NamePos: - - 135 . . . . . . . . . . Name: "content" - 136 . . . . . . . . . . Obj: *ast.Object { - 137 . . . . . . . . . . . Kind: var - 138 . . . . . . . . . . . Name: "content" - 139 . . . . . . . . . . . Decl: *(obj @ 119) - 140 . . . . . . . . . . . Data: nil - 141 . . . . . . . . . . . Type: nil - 142 . . . . . . . . . . } - 143 . . . . . . . . . } - 144 . . . . . . . . } - 145 . . . . . . . . Type: *ast.Ident { - 146 . . . . . . . . . NamePos: - - 147 . . . . . . . . . Name: "string" - 148 . . . . . . . . . Obj: nil - 149 . . . . . . . . } - 150 . . . . . . . . Tag: nil - 151 . . . . . . . . Comment: nil - 152 . . . . . . . } - 153 . . . . . . } - 154 . . . . . . Closing: - - 155 . . . . . } - 156 . . . . . Results: *ast.FieldList { - 157 . . . . . . Opening: - - 158 . . . . . . List: []*ast.Field (len = 1) { - 159 . . . . . . . 0: *ast.Field { - 160 . . . . . . . . Doc: nil - 161 . . . . . . . . Names: nil - 162 . . . . . . . . Type: *ast.Ident { - 163 . . . . . . . . . NamePos: - - 164 . . . . . . . . . Name: "error" - 165 . . . . . . . . . Obj: nil - 166 . . . . . . . . } - 167 . . . . . . . . Tag: nil - 168 . . . . . . . . Comment: nil - 169 . . . . . . . } - 170 . . . . . . } - 171 . . . . . . Closing: - - 172 . . . . . } - 173 . . . . } - 174 . . . . Tag: nil - 175 . . . . Comment: nil - 176 . . . } - 177 . . . 2: *ast.Field { - 178 . . . . Doc: *ast.CommentGroup { - 179 . . . . . List: []*ast.Comment (len = 1) { - 180 . . . . . . 0: *ast.Comment { - 181 . . . . . . . Slash: - - 182 . . . . . . . Text: "// EnsureDir makes sure a directory exists, creating it if necessary." - 183 . . . . . . } - 184 . . . . . } - 185 . . . . } - 186 . . . . Names: []*ast.Ident (len = 1) { - 187 . . . . . 0: *ast.Ident { - 188 . . . . . . NamePos: - - 189 . . . . . . Name: "EnsureDir" - 190 . . . . . . Obj: *ast.Object { - 191 . . . . . . . Kind: func - 192 . . . . . . . Name: "EnsureDir" - 193 . . . . . . . Decl: *(obj @ 177) - 194 . . . . . . . Data: nil - 195 . . . . . . . Type: nil - 196 . . . . . . } - 197 . . . . . } - 198 . . . . } - 199 . . . . Type: *ast.FuncType { - 200 . . . . . Func: - - 201 . . . . . TypeParams: nil - 202 . . . . . Params: *ast.FieldList { - 203 . . . . . . Opening: - - 204 . . . . . . List: []*ast.Field (len = 1) { - 205 . . . . . . . 0: *ast.Field { - 206 . . . . . . . . Doc: nil - 207 . . . . . . . . Names: []*ast.Ident (len = 1) { - 208 . . . . . . . . . 0: *ast.Ident { - 209 . . . . . . . . . . NamePos: - - 210 . . . . . . . . . . Name: "path" - 211 . . . . . . . . . . Obj: *ast.Object { - 212 . . . . . . . . . . . Kind: var - 213 . . . . . . . . . . . Name: "path" - 214 . . . . . . . . . . . Decl: *(obj @ 205) - 215 . . . . . . . . . . . Data: nil - 216 . . . . . . . . . . . Type: nil - 217 . . . . . . . . . . } - 218 . . . . . . . . . } - 219 . . . . . . . . } - 220 . . . . . . . . Type: *ast.Ident { - 221 . . . . . . . . . NamePos: - - 222 . . . . . . . . . Name: "string" - 223 . . . . . . . . . Obj: nil - 224 . . . . . . . . } - 225 . . . . . . . . Tag: nil - 226 . . . . . . . . Comment: nil - 227 . . . . . . . } - 228 . . . . . . } - 229 . . . . . . Closing: - - 230 . . . . . } - 231 . . . . . Results: *ast.FieldList { - 232 . . . . . . Opening: - - 233 . . . . . . List: []*ast.Field (len = 1) { - 234 . . . . . . . 0: *ast.Field { - 235 . . . . . . . . Doc: nil - 236 . . . . . . . . Names: nil - 237 . . . . . . . . Type: *ast.Ident { - 238 . . . . . . . . . NamePos: - - 239 . . . . . . . . . Name: "error" - 240 . . . . . . . . . Obj: nil - 241 . . . . . . . . } - 242 . . . . . . . . Tag: nil - 243 . . . . . . . . Comment: nil - 244 . . . . . . . } - 245 . . . . . . } - 246 . . . . . . Closing: - - 247 . . . . . } - 248 . . . . } - 249 . . . . Tag: nil - 250 . . . . Comment: nil - 251 . . . } - 252 . . . 3: *ast.Field { - 253 . . . . Doc: *ast.CommentGroup { - 254 . . . . . List: []*ast.Comment (len = 1) { - 255 . . . . . . 0: *ast.Comment { - 256 . . . . . . . Slash: - - 257 . . . . . . . Text: "// IsFile checks if a path exists and is a regular file." - 258 . . . . . . } - 259 . . . . . } - 260 . . . . } - 261 . . . . Names: []*ast.Ident (len = 1) { - 262 . . . . . 0: *ast.Ident { - 263 . . . . . . NamePos: - - 264 . . . . . . Name: "IsFile" - 265 . . . . . . Obj: *ast.Object { - 266 . . . . . . . Kind: func - 267 . . . . . . . Name: "IsFile" - 268 . . . . . . . Decl: *(obj @ 252) - 269 . . . . . . . Data: nil - 270 . . . . . . . Type: nil - 271 . . . . . . } - 272 . . . . . } - 273 . . . . } - 274 . . . . Type: *ast.FuncType { - 275 . . . . . Func: - - 276 . . . . . TypeParams: nil - 277 . . . . . Params: *ast.FieldList { - 278 . . . . . . Opening: - - 279 . . . . . . List: []*ast.Field (len = 1) { - 280 . . . . . . . 0: *ast.Field { - 281 . . . . . . . . Doc: nil - 282 . . . . . . . . Names: []*ast.Ident (len = 1) { - 283 . . . . . . . . . 0: *ast.Ident { - 284 . . . . . . . . . . NamePos: - - 285 . . . . . . . . . . Name: "path" - 286 . . . . . . . . . . Obj: *ast.Object { - 287 . . . . . . . . . . . Kind: var - 288 . . . . . . . . . . . Name: "path" - 289 . . . . . . . . . . . Decl: *(obj @ 280) - 290 . . . . . . . . . . . Data: nil - 291 . . . . . . . . . . . Type: nil - 292 . . . . . . . . . . } - 293 . . . . . . . . . } - 294 . . . . . . . . } - 295 . . . . . . . . Type: *ast.Ident { - 296 . . . . . . . . . NamePos: - - 297 . . . . . . . . . Name: "string" - 298 . . . . . . . . . Obj: nil - 299 . . . . . . . . } - 300 . . . . . . . . Tag: nil - 301 . . . . . . . . Comment: nil - 302 . . . . . . . } - 303 . . . . . . } - 304 . . . . . . Closing: - - 305 . . . . . } - 306 . . . . . Results: *ast.FieldList { - 307 . . . . . . Opening: - - 308 . . . . . . List: []*ast.Field (len = 1) { - 309 . . . . . . . 0: *ast.Field { - 310 . . . . . . . . Doc: nil - 311 . . . . . . . . Names: nil - 312 . . . . . . . . Type: *ast.Ident { - 313 . . . . . . . . . NamePos: - - 314 . . . . . . . . . Name: "bool" - 315 . . . . . . . . . Obj: nil - 316 . . . . . . . . } - 317 . . . . . . . . Tag: nil - 318 . . . . . . . . Comment: nil - 319 . . . . . . . } - 320 . . . . . . } - 321 . . . . . . Closing: - - 322 . . . . . } - 323 . . . . } - 324 . . . . Tag: nil - 325 . . . . Comment: nil - 326 . . . } - 327 . . . 4: *ast.Field { - 328 . . . . Doc: *ast.CommentGroup { - 329 . . . . . List: []*ast.Comment (len = 1) { - 330 . . . . . . 0: *ast.Comment { - 331 . . . . . . . Slash: - - 332 . . . . . . . Text: "// FileGet is a convenience function that reads a file from the medium." - 333 . . . . . . } - 334 . . . . . } - 335 . . . . } - 336 . . . . Names: []*ast.Ident (len = 1) { - 337 . . . . . 0: *ast.Ident { - 338 . . . . . . NamePos: - - 339 . . . . . . Name: "FileGet" - 340 . . . . . . Obj: *ast.Object { - 341 . . . . . . . Kind: func - 342 . . . . . . . Name: "FileGet" - 343 . . . . . . . Decl: *(obj @ 327) - 344 . . . . . . . Data: nil - 345 . . . . . . . Type: nil - 346 . . . . . . } - 347 . . . . . } - 348 . . . . } - 349 . . . . Type: *ast.FuncType { - 350 . . . . . Func: - - 351 . . . . . TypeParams: nil - 352 . . . . . Params: *ast.FieldList { - 353 . . . . . . Opening: - - 354 . . . . . . List: []*ast.Field (len = 1) { - 355 . . . . . . . 0: *ast.Field { - 356 . . . . . . . . Doc: nil - 357 . . . . . . . . Names: []*ast.Ident (len = 1) { - 358 . . . . . . . . . 0: *ast.Ident { - 359 . . . . . . . . . . NamePos: - - 360 . . . . . . . . . . Name: "path" - 361 . . . . . . . . . . Obj: *ast.Object { - 362 . . . . . . . . . . . Kind: var - 363 . . . . . . . . . . . Name: "path" - 364 . . . . . . . . . . . Decl: *(obj @ 355) - 365 . . . . . . . . . . . Data: nil - 366 . . . . . . . . . . . Type: nil - 367 . . . . . . . . . . } - 368 . . . . . . . . . } - 369 . . . . . . . . } - 370 . . . . . . . . Type: *ast.Ident { - 371 . . . . . . . . . NamePos: - - 372 . . . . . . . . . Name: "string" - 373 . . . . . . . . . Obj: nil - 374 . . . . . . . . } - 375 . . . . . . . . Tag: nil - 376 . . . . . . . . Comment: nil - 377 . . . . . . . } - 378 . . . . . . } - 379 . . . . . . Closing: - - 380 . . . . . } - 381 . . . . . Results: *ast.FieldList { - 382 . . . . . . Opening: - - 383 . . . . . . List: []*ast.Field (len = 2) { - 384 . . . . . . . 0: *ast.Field { - 385 . . . . . . . . Doc: nil - 386 . . . . . . . . Names: nil - 387 . . . . . . . . Type: *ast.Ident { - 388 . . . . . . . . . NamePos: - - 389 . . . . . . . . . Name: "string" - 390 . . . . . . . . . Obj: nil - 391 . . . . . . . . } - 392 . . . . . . . . Tag: nil - 393 . . . . . . . . Comment: nil - 394 . . . . . . . } - 395 . . . . . . . 1: *ast.Field { - 396 . . . . . . . . Doc: nil - 397 . . . . . . . . Names: nil - 398 . . . . . . . . Type: *ast.Ident { - 399 . . . . . . . . . NamePos: - - 400 . . . . . . . . . Name: "error" - 401 . . . . . . . . . Obj: nil - 402 . . . . . . . . } - 403 . . . . . . . . Tag: nil - 404 . . . . . . . . Comment: nil - 405 . . . . . . . } - 406 . . . . . . } - 407 . . . . . . Closing: - - 408 . . . . . } - 409 . . . . } - 410 . . . . Tag: nil - 411 . . . . Comment: nil - 412 . . . } - 413 . . . 5: *ast.Field { - 414 . . . . Doc: *ast.CommentGroup { - 415 . . . . . List: []*ast.Comment (len = 1) { - 416 . . . . . . 0: *ast.Comment { - 417 . . . . . . . Slash: - - 418 . . . . . . . Text: "// FileSet is a convenience function that writes a file to the medium." - 419 . . . . . . } - 420 . . . . . } - 421 . . . . } - 422 . . . . Names: []*ast.Ident (len = 1) { - 423 . . . . . 0: *ast.Ident { - 424 . . . . . . NamePos: - - 425 . . . . . . Name: "FileSet" - 426 . . . . . . Obj: *ast.Object { - 427 . . . . . . . Kind: func - 428 . . . . . . . Name: "FileSet" - 429 . . . . . . . Decl: *(obj @ 413) - 430 . . . . . . . Data: nil - 431 . . . . . . . Type: nil - 432 . . . . . . } - 433 . . . . . } - 434 . . . . } - 435 . . . . Type: *ast.FuncType { - 436 . . . . . Func: - - 437 . . . . . TypeParams: nil - 438 . . . . . Params: *ast.FieldList { - 439 . . . . . . Opening: - - 440 . . . . . . List: []*ast.Field (len = 1) { - 441 . . . . . . . 0: *ast.Field { - 442 . . . . . . . . Doc: nil - 443 . . . . . . . . Names: []*ast.Ident (len = 2) { - 444 . . . . . . . . . 0: *ast.Ident { - 445 . . . . . . . . . . NamePos: - - 446 . . . . . . . . . . Name: "path" - 447 . . . . . . . . . . Obj: *ast.Object { - 448 . . . . . . . . . . . Kind: var - 449 . . . . . . . . . . . Name: "path" - 450 . . . . . . . . . . . Decl: *(obj @ 441) - 451 . . . . . . . . . . . Data: nil - 452 . . . . . . . . . . . Type: nil - 453 . . . . . . . . . . } - 454 . . . . . . . . . } - 455 . . . . . . . . . 1: *ast.Ident { - 456 . . . . . . . . . . NamePos: - - 457 . . . . . . . . . . Name: "content" - 458 . . . . . . . . . . Obj: *ast.Object { - 459 . . . . . . . . . . . Kind: var - 460 . . . . . . . . . . . Name: "content" - 461 . . . . . . . . . . . Decl: *(obj @ 441) - 462 . . . . . . . . . . . Data: nil - 463 . . . . . . . . . . . Type: nil - 464 . . . . . . . . . . } - 465 . . . . . . . . . } - 466 . . . . . . . . } - 467 . . . . . . . . Type: *ast.Ident { - 468 . . . . . . . . . NamePos: - - 469 . . . . . . . . . Name: "string" - 470 . . . . . . . . . Obj: nil - 471 . . . . . . . . } - 472 . . . . . . . . Tag: nil - 473 . . . . . . . . Comment: nil - 474 . . . . . . . } - 475 . . . . . . } - 476 . . . . . . Closing: - - 477 . . . . . } - 478 . . . . . Results: *ast.FieldList { - 479 . . . . . . Opening: - - 480 . . . . . . List: []*ast.Field (len = 1) { - 481 . . . . . . . 0: *ast.Field { - 482 . . . . . . . . Doc: nil - 483 . . . . . . . . Names: nil - 484 . . . . . . . . Type: *ast.Ident { - 485 . . . . . . . . . NamePos: - - 486 . . . . . . . . . Name: "error" - 487 . . . . . . . . . Obj: nil - 488 . . . . . . . . } - 489 . . . . . . . . Tag: nil - 490 . . . . . . . . Comment: nil - 491 . . . . . . . } - 492 . . . . . . } - 493 . . . . . . Closing: - - 494 . . . . . } - 495 . . . . } - 496 . . . . Tag: nil - 497 . . . . Comment: nil - 498 . . . } - 499 . . } - 500 . . Closing: - - 501 . } - 502 . Incomplete: false - 503 } +type Medium interface { + // Read retrieves the content of a file as a string. + Read(path string) (string, error) + // Write saves the given content to a file, overwriting it if it exists. + Write(path, content string) error + + // EnsureDir makes sure a directory exists, creating it if necessary. + EnsureDir(path string) error + + // IsFile checks if a path exists and is a regular file. + IsFile(path string) bool + + // FileGet is a convenience function that reads a file from the medium. + FileGet(path string) (string, error) + + // FileSet is a convenience function that writes a file to the medium. + FileSet(path, content string) error +} ``` -Medium defines the standard interface for a storage backend. -This allows for different implementations (e.g., local disk, S3, SFTP) -to be used interchangeably. - - - - ### `type MockMedium` + +`MockMedium` implements the `Medium` interface for testing purposes. + ```go -type MockMedium 0 *ast.StructType { - 1 . Struct: - - 2 . Fields: *ast.FieldList { - 3 . . Opening: - - 4 . . List: []*ast.Field (len = 2) { - 5 . . . 0: *ast.Field { - 6 . . . . Doc: nil - 7 . . . . Names: []*ast.Ident (len = 1) { - 8 . . . . . 0: *ast.Ident { - 9 . . . . . . NamePos: - - 10 . . . . . . Name: "Files" - 11 . . . . . . Obj: *ast.Object { - 12 . . . . . . . Kind: var - 13 . . . . . . . Name: "Files" - 14 . . . . . . . Decl: *(obj @ 5) - 15 . . . . . . . Data: nil - 16 . . . . . . . Type: nil - 17 . . . . . . } - 18 . . . . . } - 19 . . . . } - 20 . . . . Type: *ast.MapType { - 21 . . . . . Map: - - 22 . . . . . Key: *ast.Ident { - 23 . . . . . . NamePos: - - 24 . . . . . . Name: "string" - 25 . . . . . . Obj: nil - 26 . . . . . } - 27 . . . . . Value: *ast.Ident { - 28 . . . . . . NamePos: - - 29 . . . . . . Name: "string" - 30 . . . . . . Obj: nil - 31 . . . . . } - 32 . . . . } - 33 . . . . Tag: nil - 34 . . . . Comment: nil - 35 . . . } - 36 . . . 1: *ast.Field { - 37 . . . . Doc: nil - 38 . . . . Names: []*ast.Ident (len = 1) { - 39 . . . . . 0: *ast.Ident { - 40 . . . . . . NamePos: - - 41 . . . . . . Name: "Dirs" - 42 . . . . . . Obj: *ast.Object { - 43 . . . . . . . Kind: var - 44 . . . . . . . Name: "Dirs" - 45 . . . . . . . Decl: *(obj @ 36) - 46 . . . . . . . Data: nil - 47 . . . . . . . Type: nil - 48 . . . . . . } - 49 . . . . . } - 50 . . . . } - 51 . . . . Type: *ast.MapType { - 52 . . . . . Map: - - 53 . . . . . Key: *ast.Ident { - 54 . . . . . . NamePos: - - 55 . . . . . . Name: "string" - 56 . . . . . . Obj: nil - 57 . . . . . } - 58 . . . . . Value: *ast.Ident { - 59 . . . . . . NamePos: - - 60 . . . . . . Name: "bool" - 61 . . . . . . Obj: nil - 62 . . . . . } - 63 . . . . } - 64 . . . . Tag: nil - 65 . . . . Comment: nil - 66 . . . } - 67 . . } - 68 . . Closing: - - 69 . } - 70 . Incomplete: false - 71 } - +type MockMedium struct { + Files map[string]string + Dirs map[string]bool +} ``` -MockMedium implements the Medium interface for testing purposes. - - #### Methods -- `EnsureDir(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: - -- `FileGet(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: - -- `FileSet(path, content 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: - -- `IsFile(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "bool" - 3 . Obj: nil - 4 } -`: - -- `Read(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: - -- `Write(path, content 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: - - - - +- `EnsureDir(path string) error`: Mocks creating a directory. +- `FileGet(path string) (string, error)`: Mocks reading a file. +- `FileSet(path, content string) error`: Mocks writing a file. +- `IsFile(path string) bool`: Mocks checking if a path is a file. +- `Read(path string) (string, error)`: Mocks reading a file. +- `Write(path, content string) error`: Mocks writing a file. ## Functions -- `Copy(sourceMedium 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "Medium" - 3 . Obj: nil - 4 } -, sourcePath 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, destMedium 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "Medium" - 3 . Obj: nil - 4 } -, destPath 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Copy copies a file from a source medium to a destination medium. +- `Copy(sourceMedium Medium, sourcePath string, destMedium Medium, destPath string) error`: Copies a file from a source medium to a destination medium. +- `EnsureDir(m Medium, path string) error`: Ensures a directory exists on the given medium. +- `IsFile(m Medium, path string) bool`: Checks if a path is a file on the given medium. +- `Read(m Medium, path string) (string, error)`: Retrieves the content of a file from the given medium. +- `Write(m Medium, path, content string) error`: Saves content to a file on the given medium. -- `EnsureDir(m 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "Medium" - 3 . Obj: nil - 4 } -, path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: EnsureDir ensures a directory exists on the given medium. - -- `IsFile(m 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "Medium" - 3 . Obj: nil - 4 } -, path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "bool" - 3 . Obj: nil - 4 } -`: IsFile checks if a path is a file on the given medium. - -- `Read(m 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "Medium" - 3 . Obj: nil - 4 } -, path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Read retrieves the content of a file from the given medium. - -- `Write(m 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "Medium" - 3 . Obj: nil - 4 } -, path, content 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Write saves content to a file on the given medium. +Would you like to see some examples of how to use this service? diff --git a/docs/services/workspace.md b/docs/services/workspace.md index 3d46c31b..dd44307e 100644 --- a/docs/services/workspace.md +++ b/docs/services/workspace.md @@ -3,626 +3,74 @@ title: workspace --- # Service: `workspace` - - - ## Constants -```godefaultWorkspacelistFile +```go +defaultWorkspacelistFile ``` - - - ## Types ### `type Options` + ```go -type Options 0 *ast.StructType { - 1 . Struct: - - 2 . Fields: *ast.FieldList { - 3 . . Opening: - - 4 . . List: nil - 5 . . Closing: - - 6 . } - 7 . Incomplete: false - 8 } - +type Options struct { + // Options holds configuration for the workspace service. +} ``` -Options holds configuration for the workspace service. - - - - ### `type Service` -```go -type Service 0 *ast.StructType { - 1 . Struct: - - 2 . Fields: *ast.FieldList { - 3 . . Opening: - - 4 . . List: []*ast.Field (len = 4) { - 5 . . . 0: *ast.Field { - 6 . . . . Doc: nil - 7 . . . . Names: nil - 8 . . . . Type: *ast.StarExpr { - 9 . . . . . Star: - - 10 . . . . . X: *ast.IndexExpr { - 11 . . . . . . X: *ast.SelectorExpr { - 12 . . . . . . . X: *ast.Ident { - 13 . . . . . . . . NamePos: - - 14 . . . . . . . . Name: "core" - 15 . . . . . . . . Obj: nil - 16 . . . . . . . } - 17 . . . . . . . Sel: *ast.Ident { - 18 . . . . . . . . NamePos: - - 19 . . . . . . . . Name: "Runtime" - 20 . . . . . . . . Obj: nil - 21 . . . . . . . } - 22 . . . . . . } - 23 . . . . . . Lbrack: - - 24 . . . . . . Index: *ast.Ident { - 25 . . . . . . . NamePos: - - 26 . . . . . . . Name: "Options" - 27 . . . . . . . Obj: *ast.Object { - 28 . . . . . . . . Kind: type - 29 . . . . . . . . Name: "Options" - 30 . . . . . . . . Decl: *ast.TypeSpec { - 31 . . . . . . . . . Doc: nil - 32 . . . . . . . . . Name: *ast.Ident { - 33 . . . . . . . . . . NamePos: - - 34 . . . . . . . . . . Name: "Options" - 35 . . . . . . . . . . Obj: *(obj @ 27) - 36 . . . . . . . . . } - 37 . . . . . . . . . TypeParams: nil - 38 . . . . . . . . . Assign: - - 39 . . . . . . . . . Type: *ast.StructType { - 40 . . . . . . . . . . Struct: - - 41 . . . . . . . . . . Fields: *ast.FieldList { - 42 . . . . . . . . . . . Opening: - - 43 . . . . . . . . . . . List: nil - 44 . . . . . . . . . . . Closing: - - 45 . . . . . . . . . . } - 46 . . . . . . . . . . Incomplete: false - 47 . . . . . . . . . } - 48 . . . . . . . . . Comment: nil - 49 . . . . . . . . } - 50 . . . . . . . . Data: nil - 51 . . . . . . . . Type: nil - 52 . . . . . . . } - 53 . . . . . . } - 54 . . . . . . Rbrack: - - 55 . . . . . } - 56 . . . . } - 57 . . . . Tag: nil - 58 . . . . Comment: nil - 59 . . . } - 60 . . . 1: *ast.Field { - 61 . . . . Doc: nil - 62 . . . . Names: []*ast.Ident (len = 1) { - 63 . . . . . 0: *ast.Ident { - 64 . . . . . . NamePos: - - 65 . . . . . . Name: "activeWorkspace" - 66 . . . . . . Obj: *ast.Object { - 67 . . . . . . . Kind: var - 68 . . . . . . . Name: "activeWorkspace" - 69 . . . . . . . Decl: *(obj @ 60) - 70 . . . . . . . Data: nil - 71 . . . . . . . Type: nil - 72 . . . . . . } - 73 . . . . . } - 74 . . . . } - 75 . . . . Type: *ast.StarExpr { - 76 . . . . . Star: - - 77 . . . . . X: *ast.Ident { - 78 . . . . . . NamePos: - - 79 . . . . . . Name: "Workspace" - 80 . . . . . . Obj: *ast.Object { - 81 . . . . . . . Kind: type - 82 . . . . . . . Name: "Workspace" - 83 . . . . . . . Decl: *ast.TypeSpec { - 84 . . . . . . . . Doc: nil - 85 . . . . . . . . Name: *ast.Ident { - 86 . . . . . . . . . NamePos: - - 87 . . . . . . . . . Name: "Workspace" - 88 . . . . . . . . . Obj: *(obj @ 80) - 89 . . . . . . . . } - 90 . . . . . . . . TypeParams: nil - 91 . . . . . . . . Assign: - - 92 . . . . . . . . Type: *ast.StructType { - 93 . . . . . . . . . Struct: - - 94 . . . . . . . . . Fields: *ast.FieldList { - 95 . . . . . . . . . . Opening: - - 96 . . . . . . . . . . List: []*ast.Field (len = 2) { - 97 . . . . . . . . . . . 0: *ast.Field { - 98 . . . . . . . . . . . . Doc: nil - 99 . . . . . . . . . . . . Names: []*ast.Ident (len = 1) { - 100 . . . . . . . . . . . . . 0: *ast.Ident { - 101 . . . . . . . . . . . . . . NamePos: - - 102 . . . . . . . . . . . . . . Name: "Name" - 103 . . . . . . . . . . . . . . Obj: *ast.Object { - 104 . . . . . . . . . . . . . . . Kind: var - 105 . . . . . . . . . . . . . . . Name: "Name" - 106 . . . . . . . . . . . . . . . Decl: *(obj @ 97) - 107 . . . . . . . . . . . . . . . Data: nil - 108 . . . . . . . . . . . . . . . Type: nil - 109 . . . . . . . . . . . . . . } - 110 . . . . . . . . . . . . . } - 111 . . . . . . . . . . . . } - 112 . . . . . . . . . . . . Type: *ast.Ident { - 113 . . . . . . . . . . . . . NamePos: - - 114 . . . . . . . . . . . . . Name: "string" - 115 . . . . . . . . . . . . . Obj: nil - 116 . . . . . . . . . . . . } - 117 . . . . . . . . . . . . Tag: nil - 118 . . . . . . . . . . . . Comment: nil - 119 . . . . . . . . . . . } - 120 . . . . . . . . . . . 1: *ast.Field { - 121 . . . . . . . . . . . . Doc: nil - 122 . . . . . . . . . . . . Names: []*ast.Ident (len = 1) { - 123 . . . . . . . . . . . . . 0: *ast.Ident { - 124 . . . . . . . . . . . . . . NamePos: - - 125 . . . . . . . . . . . . . . Name: "Path" - 126 . . . . . . . . . . . . . . Obj: *ast.Object { - 127 . . . . . . . . . . . . . . . Kind: var - 128 . . . . . . . . . . . . . . . Name: "Path" - 129 . . . . . . . . . . . . . . . Decl: *(obj @ 120) - 130 . . . . . . . . . . . . . . . Data: nil - 131 . . . . . . . . . . . . . . . Type: nil - 132 . . . . . . . . . . . . . . } - 133 . . . . . . . . . . . . . } - 134 . . . . . . . . . . . . } - 135 . . . . . . . . . . . . Type: *ast.Ident { - 136 . . . . . . . . . . . . . NamePos: - - 137 . . . . . . . . . . . . . Name: "string" - 138 . . . . . . . . . . . . . Obj: nil - 139 . . . . . . . . . . . . } - 140 . . . . . . . . . . . . Tag: nil - 141 . . . . . . . . . . . . Comment: nil - 142 . . . . . . . . . . . } - 143 . . . . . . . . . . } - 144 . . . . . . . . . . Closing: - - 145 . . . . . . . . . } - 146 . . . . . . . . . Incomplete: false - 147 . . . . . . . . } - 148 . . . . . . . . Comment: nil - 149 . . . . . . . } - 150 . . . . . . . Data: nil - 151 . . . . . . . Type: nil - 152 . . . . . . } - 153 . . . . . } - 154 . . . . } - 155 . . . . Tag: nil - 156 . . . . Comment: nil - 157 . . . } - 158 . . . 2: *ast.Field { - 159 . . . . Doc: nil - 160 . . . . Names: []*ast.Ident (len = 1) { - 161 . . . . . 0: *ast.Ident { - 162 . . . . . . NamePos: - - 163 . . . . . . Name: "workspaceList" - 164 . . . . . . Obj: *ast.Object { - 165 . . . . . . . Kind: var - 166 . . . . . . . Name: "workspaceList" - 167 . . . . . . . Decl: *(obj @ 158) - 168 . . . . . . . Data: nil - 169 . . . . . . . Type: nil - 170 . . . . . . } - 171 . . . . . } - 172 . . . . } - 173 . . . . Type: *ast.MapType { - 174 . . . . . Map: - - 175 . . . . . Key: *ast.Ident { - 176 . . . . . . NamePos: - - 177 . . . . . . Name: "string" - 178 . . . . . . Obj: nil - 179 . . . . . } - 180 . . . . . Value: *ast.Ident { - 181 . . . . . . NamePos: - - 182 . . . . . . Name: "string" - 183 . . . . . . Obj: nil - 184 . . . . . } - 185 . . . . } - 186 . . . . Tag: nil - 187 . . . . Comment: *ast.CommentGroup { - 188 . . . . . List: []*ast.Comment (len = 1) { - 189 . . . . . . 0: *ast.Comment { - 190 . . . . . . . Slash: - - 191 . . . . . . . Text: "// Maps Workspace ID to Public Key" - 192 . . . . . . } - 193 . . . . . } - 194 . . . . } - 195 . . . } - 196 . . . 3: *ast.Field { - 197 . . . . Doc: nil - 198 . . . . Names: []*ast.Ident (len = 1) { - 199 . . . . . 0: *ast.Ident { - 200 . . . . . . NamePos: - - 201 . . . . . . Name: "medium" - 202 . . . . . . Obj: *ast.Object { - 203 . . . . . . . Kind: var - 204 . . . . . . . Name: "medium" - 205 . . . . . . . Decl: *(obj @ 196) - 206 . . . . . . . Data: nil - 207 . . . . . . . Type: nil - 208 . . . . . . } - 209 . . . . . } - 210 . . . . } - 211 . . . . Type: *ast.SelectorExpr { - 212 . . . . . X: *ast.Ident { - 213 . . . . . . NamePos: - - 214 . . . . . . Name: "io" - 215 . . . . . . Obj: nil - 216 . . . . . } - 217 . . . . . Sel: *ast.Ident { - 218 . . . . . . NamePos: - - 219 . . . . . . Name: "Medium" - 220 . . . . . . Obj: nil - 221 . . . . . } - 222 . . . . } - 223 . . . . Tag: nil - 224 . . . . Comment: nil - 225 . . . } - 226 . . } - 227 . . Closing: - - 228 . } - 229 . Incomplete: false - 230 } +```go +type Service struct { + core.Runtime[*Options] + activeWorkspace *Workspace + workspaceList map[string]string // Maps Workspace ID to Public Key + medium io.Medium +} ``` + Service manages user workspaces. - - #### Methods -- `CreateWorkspace(identifier, password 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: CreateWorkspace creates a new, obfuscated workspace on the local medium. - -- `HandleIPCEvents(c 0 *ast.StarExpr { - 1 . Star: - - 2 . X: *ast.SelectorExpr { - 3 . . X: *ast.Ident { - 4 . . . NamePos: - - 5 . . . Name: "core" - 6 . . . Obj: nil - 7 . . } - 8 . . Sel: *ast.Ident { - 9 . . . NamePos: - - 10 . . . Name: "Core" - 11 . . . Obj: nil - 12 . . } - 13 . } - 14 } -, msg 0 *ast.SelectorExpr { - 1 . X: *ast.Ident { - 2 . . NamePos: - - 3 . . Name: "core" - 4 . . Obj: nil - 5 . } - 6 . Sel: *ast.Ident { - 7 . . NamePos: - - 8 . . Name: "Message" - 9 . . Obj: nil - 10 . } - 11 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: HandleIPCEvents processes IPC messages, including injecting dependencies on startup. - -- `ServiceStartup( 0 *ast.SelectorExpr { - 1 . X: *ast.Ident { - 2 . . NamePos: - - 3 . . Name: "context" - 4 . . Obj: nil - 5 . } - 6 . Sel: *ast.Ident { - 7 . . NamePos: - - 8 . . Name: "Context" - 9 . . Obj: nil - 10 . } - 11 } -, 0 *ast.SelectorExpr { - 1 . X: *ast.Ident { - 2 . . NamePos: - - 3 . . Name: "application" - 4 . . Obj: nil - 5 . } - 6 . Sel: *ast.Ident { - 7 . . NamePos: - - 8 . . Name: "ServiceOptions" - 9 . . Obj: nil - 10 . } - 11 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: ServiceStartup initializes the service, loading the workspace list. - -- `SwitchWorkspace(name 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: SwitchWorkspace changes the active workspace. - -- `WorkspaceFileGet(filename 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: WorkspaceFileGet retrieves a file from the active workspace. - -- `WorkspaceFileSet(filename, content 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: WorkspaceFileSet writes a file to the active workspace. - -- `getWorkspaceDir() 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: getWorkspaceDir retrieves the WorkspaceDir from the config service. - - +- `CreateWorkspace(identifier, password string) (string, error)`: CreateWorkspace creates a new, obfuscated workspace on the local medium. +- `HandleIPCEvents(c *core.Core, msg core.Message) error`: HandleIPCEvents processes IPC messages, including injecting dependencies on startup. +- `ServiceStartup(context.Context, application.ServiceOptions) error`: ServiceStartup initializes the service, loading the workspace list. +- `SwitchWorkspace(name string) error`: SwitchWorkspace changes the active workspace. +- `WorkspaceFileGet(filename string) (string, error)`: WorkspaceFileGet retrieves a file from the active workspace. +- `WorkspaceFileSet(filename, content string) error`: WorkspaceFileSet writes a file to the active workspace. +- `getWorkspaceDir() (string, error)`: getWorkspaceDir retrieves the WorkspaceDir from the config service. ### `type Workspace` -```go -type Workspace 0 *ast.StructType { - 1 . Struct: - - 2 . Fields: *ast.FieldList { - 3 . . Opening: - - 4 . . List: []*ast.Field (len = 2) { - 5 . . . 0: *ast.Field { - 6 . . . . Doc: nil - 7 . . . . Names: []*ast.Ident (len = 1) { - 8 . . . . . 0: *ast.Ident { - 9 . . . . . . NamePos: - - 10 . . . . . . Name: "Name" - 11 . . . . . . Obj: *ast.Object { - 12 . . . . . . . Kind: var - 13 . . . . . . . Name: "Name" - 14 . . . . . . . Decl: *(obj @ 5) - 15 . . . . . . . Data: nil - 16 . . . . . . . Type: nil - 17 . . . . . . } - 18 . . . . . } - 19 . . . . } - 20 . . . . Type: *ast.Ident { - 21 . . . . . NamePos: - - 22 . . . . . Name: "string" - 23 . . . . . Obj: nil - 24 . . . . } - 25 . . . . Tag: nil - 26 . . . . Comment: nil - 27 . . . } - 28 . . . 1: *ast.Field { - 29 . . . . Doc: nil - 30 . . . . Names: []*ast.Ident (len = 1) { - 31 . . . . . 0: *ast.Ident { - 32 . . . . . . NamePos: - - 33 . . . . . . Name: "Path" - 34 . . . . . . Obj: *ast.Object { - 35 . . . . . . . Kind: var - 36 . . . . . . . Name: "Path" - 37 . . . . . . . Decl: *(obj @ 28) - 38 . . . . . . . Data: nil - 39 . . . . . . . Type: nil - 40 . . . . . . } - 41 . . . . . } - 42 . . . . } - 43 . . . . Type: *ast.Ident { - 44 . . . . . NamePos: - - 45 . . . . . Name: "string" - 46 . . . . . Obj: nil - 47 . . . . } - 48 . . . . Tag: nil - 49 . . . . Comment: nil - 50 . . . } - 51 . . } - 52 . . Closing: - - 53 . } - 54 . Incomplete: false - 55 } +```go +type Workspace struct { + Name string + Path string +} ``` + Workspace represents a user's workspace. - - - - ### `type localMedium` + ```go -type localMedium 0 *ast.StructType { - 1 . Struct: - - 2 . Fields: *ast.FieldList { - 3 . . Opening: - - 4 . . List: nil - 5 . . Closing: - - 6 . } - 7 . Incomplete: false - 8 } - +type localMedium struct{} ``` + localMedium implements the Medium interface for the local disk. - - #### Methods -- `EnsureDir(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: EnsureDir creates a directory on the local disk. - -- `FileGet(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: FileGet reads a file from the local disk. - -- `FileSet(path, content 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: FileSet writes a file to the local disk. - -- `IsFile(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "bool" - 3 . Obj: nil - 4 } -`: IsFile checks if a path exists and is a file on the local disk. - -- `Read(path 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Read reads a file from the local disk. - -- `Write(path, content 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "string" - 3 . Obj: nil - 4 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Write writes a file to the local disk. - - - - +- `EnsureDir(path string) error`: EnsureDir creates a directory on the local disk. +- `FileGet(path string) (string, error)`: FileGet reads a file from the local disk. +- `FileSet(path, content string) error`: FileSet writes a file to the local disk. +- `IsFile(path string) bool`: IsFile checks if a path exists and is a file on the local disk. +- `Read(path string) (string, error)`: Read reads a file from the local disk. +- `Write(path, content string) error`: Write writes a file to the local disk. ## Functions -- `NewLocalMedium() 0 *ast.SelectorExpr { - 1 . X: *ast.Ident { - 2 . . NamePos: - - 3 . . Name: "io" - 4 . . Obj: nil - 5 . } - 6 . Sel: *ast.Ident { - 7 . . NamePos: - - 8 . . Name: "Medium" - 9 . . Obj: nil - 10 . } - 11 } -`: NewLocalMedium creates a new instance of the local storage medium. - -- `Register(c 0 *ast.StarExpr { - 1 . Star: - - 2 . X: *ast.SelectorExpr { - 3 . . X: *ast.Ident { - 4 . . . NamePos: - - 5 . . . Name: "core" - 6 . . . Obj: nil - 7 . . } - 8 . . Sel: *ast.Ident { - 9 . . . NamePos: - - 10 . . . Name: "Core" - 11 . . . Obj: nil - 12 . . } - 13 . } - 14 } -) 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "any" - 3 . Obj: nil - 4 } -, 0 *ast.Ident { - 1 . NamePos: - - 2 . Name: "error" - 3 . Obj: nil - 4 } -`: Register is the constructor for dynamic dependency injection (used with core.WithService). It creates a Service instance and initializes its core.Runtime field. Dependencies are injected during ServiceStartup. +- `NewLocalMedium() io.Medium`: NewLocalMedium creates a new instance of the local storage medium. +- `Register(c *core.Core) (any, error)`: Register is the constructor for dynamic dependency injection (used with core.WithService). It creates a Service instance and initializes its core.Runtime field. Dependencies are injected during ServiceStartup. diff --git a/pkg/core/core_test.go b/pkg/core/core_test.go index b6314f82..bab401b3 100644 --- a/pkg/core/core_test.go +++ b/pkg/core/core_test.go @@ -1,19 +1,21 @@ package core import ( - "errors" + "embed" + "io" "testing" - "github.com/Snider/Core/pkg/core/testutil" "github.com/stretchr/testify/assert" + "github.com/wailsapp/wails/v3/pkg/application" ) -// MockServiceInterface is an interface that MockService implements. -type MockServiceInterface interface { - GetName() string +func TestCore_New_Good(t *testing.T) { + c, err := New() + assert.NoError(t, err) + assert.NotNil(t, c) } -// MockService is a simple struct to act as a mock service. +// Mock service for testing type MockService struct { Name string } @@ -22,166 +24,172 @@ func (m *MockService) GetName() string { return m.Name } -func TestNew(t *testing.T) { - c, err := New() +func TestCore_WithService_Good(t *testing.T) { + factory := func(c *Core) (any, error) { + return &MockService{Name: "test"}, nil + } + c, err := New(WithService(factory)) assert.NoError(t, err) - assert.NotNil(t, c) - assert.NotNil(t, c.services) - assert.False(t, c.servicesLocked) + svc := c.Service("core") + assert.NotNil(t, svc) + mockSvc, ok := svc.(*MockService) + assert.True(t, ok) + assert.Equal(t, "test", mockSvc.GetName()) } -func TestWithService(t *testing.T) { - // Test successful service registration - t.Run("successful service registration", func(t *testing.T) { - c, err := New() - assert.NoError(t, err) - - factory := func(c *Core) (any, error) { - return &MockService{Name: "testService"}, nil - } - - err = WithService(factory)(c) - assert.NoError(t, err) - - // The service name is derived from the package path of MockService, which is "core" - svc := c.Service("core") - assert.NotNil(t, svc) - mockSvc, ok := svc.(*MockService) - assert.True(t, ok) - assert.Equal(t, "testService", mockSvc.Name) - }) - - // Test service registration with factory error - t.Run("service registration with factory error", func(t *testing.T) { - c, err := New() - assert.NoError(t, err) - - factory := func(c *Core) (any, error) { - return nil, errors.New("factory error") - } - - err = WithService(factory)(c) - assert.Error(t, err) - assert.Contains(t, err.Error(), "factory error") - }) - - // Test service registration when services are locked - t.Run("service registration when locked", func(t *testing.T) { - c, err := New(WithServiceLock()) - assert.NoError(t, err) - - factory := func(c *Core) (any, error) { - return &MockService{Name: "lockedService"}, nil - } - - err = WithService(factory)(c) - assert.Error(t, err) - assert.Contains(t, err.Error(), "is not permitted by the serviceLock setting") - }) -} - -func TestServiceFor(t *testing.T) { - c, err := New() - assert.NoError(t, err) - - // Register a mock service - err = c.RegisterService("mockservice", &MockService{Name: "testService"}) - assert.NoError(t, err) - - // Test successful retrieval as an interface - t.Run("successful retrieval as interface", func(t *testing.T) { - svc, err := ServiceFor[MockServiceInterface](c, "mockservice") - assert.NoError(t, err) - assert.Equal(t, "testService", svc.GetName()) - }) - - // Test service not found - t.Run("service not found", func(t *testing.T) { - _, err := ServiceFor[MockServiceInterface](c, "nonexistent") - assert.Error(t, err) - assert.Contains(t, err.Error(), "service 'nonexistent' not found") - }) - - // Test type mismatch - t.Run("type mismatch", func(t *testing.T) { - err := c.RegisterService("stringservice", "hello") - assert.NoError(t, err) - _, err = ServiceFor[MockServiceInterface](c, "stringservice") - assert.Error(t, err) - assert.Contains(t, err.Error(), "is of type string, but expected ") - }) -} - -func TestMustServiceFor(t *testing.T) { - c, err := New() - assert.NoError(t, err) - - // Register a mock service - assert.NoError(t, c.RegisterService("mockservice", &MockService{Name: "testService"})) - - // Test successful retrieval as an interface - assert.NotPanics(t, func() { - svc := MustServiceFor[MockServiceInterface](c, "mockservice") - assert.Equal(t, "testService", svc.GetName()) - }) - - // Test service not found (should panic) - assert.Panics(t, func() { - MustServiceFor[MockServiceInterface](c, "nonexistent") - }) - - // Test type mismatch (should panic) - assert.NoError(t, c.RegisterService("stringservice", "hello")) - assert.Panics(t, func() { - MustServiceFor[MockServiceInterface](c, "stringservice") - }) -} - -func TestRegisterService(t *testing.T) { - c, err := New() - assert.NoError(t, err) - - // Test successful registration - err = c.RegisterService("myservice", &MockService{}) - assert.NoError(t, err) - assert.NotNil(t, c.Service("myservice")) - - // Test duplicate registration - err = c.RegisterService("myservice", &MockService{}) +func TestCore_WithService_Bad(t *testing.T) { + factory := func(c *Core) (any, error) { + return nil, assert.AnError + } + _, err := New(WithService(factory)) assert.Error(t, err) - assert.Contains(t, err.Error(), "already registered") + assert.ErrorIs(t, err, assert.AnError) +} - // Test empty name +func TestCore_WithWails_Good(t *testing.T) { + app := &application.App{} + c, err := New(WithWails(app)) + assert.NoError(t, err) + assert.Equal(t, app, c.App) +} + +//go:embed testdata +var testFS embed.FS + +func TestCore_WithAssets_Good(t *testing.T) { + c, err := New(WithAssets(testFS)) + assert.NoError(t, err) + assets := c.Assets() + file, err := assets.Open("testdata/test.txt") + assert.NoError(t, err) + defer file.Close() + content, err := io.ReadAll(file) + assert.NoError(t, err) + assert.Equal(t, "hello from testdata\n", string(content)) +} + +func TestCore_WithServiceLock_Good(t *testing.T) { + c, err := New(WithServiceLock()) + assert.NoError(t, err) + err = c.RegisterService("test", &MockService{}) + assert.Error(t, err) +} + +func TestCore_RegisterService_Good(t *testing.T) { + c, err := New() + assert.NoError(t, err) + err = c.RegisterService("test", &MockService{Name: "test"}) + assert.NoError(t, err) + svc := c.Service("test") + assert.NotNil(t, svc) + mockSvc, ok := svc.(*MockService) + assert.True(t, ok) + assert.Equal(t, "test", mockSvc.GetName()) +} + +func TestCore_RegisterService_Bad(t *testing.T) { + c, err := New() + assert.NoError(t, err) + err = c.RegisterService("test", &MockService{}) + assert.NoError(t, err) + err = c.RegisterService("test", &MockService{}) + assert.Error(t, err) err = c.RegisterService("", &MockService{}) assert.Error(t, err) - assert.Contains(t, err.Error(), "service name cannot be empty") - - // Test registration when locked - lockedCore, err := New(WithServiceLock()) - assert.NoError(t, err) - err = lockedCore.RegisterService("lockedservice", &MockService{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "is not permitted by the serviceLock setting") } -func TestCoreConfig(t *testing.T) { +func TestCore_ServiceFor_Good(t *testing.T) { c, err := New() assert.NoError(t, err) - - // Register a mock config service - mockCfg := &testutil.MockConfig{} - err = c.RegisterService("config", mockCfg) + err = c.RegisterService("test", &MockService{Name: "test"}) assert.NoError(t, err) + svc, err := ServiceFor[*MockService](c, "test") + assert.NoError(t, err) + assert.Equal(t, "test", svc.GetName()) +} - // Test successful retrieval of Config service - cfg := c.Config() - assert.NotNil(t, cfg) - assert.Implements(t, (*Config)(nil), cfg) +func TestCore_ServiceFor_Bad(t *testing.T) { + c, err := New() + assert.NoError(t, err) + _, err = ServiceFor[*MockService](c, "nonexistent") + assert.Error(t, err) + err = c.RegisterService("test", "not a service") + assert.NoError(t, err) + _, err = ServiceFor[*MockService](c, "test") + assert.Error(t, err) +} - // Test panic if Config service not registered - coreWithoutConfig, err := New() +func TestCore_MustServiceFor_Good(t *testing.T) { + c, err := New() + assert.NoError(t, err) + err = c.RegisterService("test", &MockService{Name: "test"}) + assert.NoError(t, err) + svc := MustServiceFor[*MockService](c, "test") + assert.Equal(t, "test", svc.GetName()) +} + +func TestCore_MustServiceFor_Ugly(t *testing.T) { + c, err := New() assert.NoError(t, err) assert.Panics(t, func() { - coreWithoutConfig.Config() + MustServiceFor[*MockService](c, "nonexistent") + }) + err = c.RegisterService("test", "not a service") + assert.NoError(t, err) + assert.Panics(t, func() { + MustServiceFor[*MockService](c, "test") }) } + +type MockAction struct { + handled bool +} + +func (a *MockAction) Handle(c *Core, msg Message) error { + a.handled = true + return nil +} + +func TestCore_ACTION_Good(t *testing.T) { + c, err := New() + assert.NoError(t, err) + action := &MockAction{} + c.RegisterAction(action.Handle) + err = c.ACTION(nil) + assert.NoError(t, err) + assert.True(t, action.handled) +} + +func TestCore_RegisterActions_Good(t *testing.T) { + c, err := New() + assert.NoError(t, err) + action1 := &MockAction{} + action2 := &MockAction{} + c.RegisterActions(action1.Handle, action2.Handle) + err = c.ACTION(nil) + assert.NoError(t, err) + assert.True(t, action1.handled) + assert.True(t, action2.handled) +} + +func TestCore_WithName_Good(t *testing.T) { + factory := func(c *Core) (any, error) { + return &MockService{Name: "test"}, nil + } + c, err := New(WithName("my-service", factory)) + assert.NoError(t, err) + svc := c.Service("my-service") + assert.NotNil(t, svc) + mockSvc, ok := svc.(*MockService) + assert.True(t, ok) + assert.Equal(t, "test", mockSvc.GetName()) +} + +func TestCore_WithName_Bad(t *testing.T) { + factory := func(c *Core) (any, error) { + return nil, assert.AnError + } + _, err := New(WithName("my-service", factory)) + assert.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) +} diff --git a/tdd/testdata/test.txt b/pkg/core/testdata/test.txt similarity index 100% rename from tdd/testdata/test.txt rename to pkg/core/testdata/test.txt diff --git a/pkg/io/io_test.go b/pkg/io/io_test.go new file mode 100644 index 00000000..aad8db1b --- /dev/null +++ b/pkg/io/io_test.go @@ -0,0 +1,87 @@ +package io + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIO_Read_Good(t *testing.T) { + medium := NewMockMedium() + medium.Files["test.txt"] = "hello" + + content, err := Read(medium, "test.txt") + assert.NoError(t, err) + assert.Equal(t, "hello", content) +} + +func TestIO_Read_Bad(t *testing.T) { + medium := NewMockMedium() + + _, err := Read(medium, "nonexistent.txt") + assert.Error(t, err) +} + +func TestIO_Write_Good(t *testing.T) { + medium := NewMockMedium() + + err := Write(medium, "test.txt", "hello") + assert.NoError(t, err) + + writtenContent, ok := medium.Files["test.txt"] + assert.True(t, ok) + assert.Equal(t, "hello", writtenContent) +} + +// TODO: The current MockMedium cannot simulate a write error. +// func TestIO_Write_Bad(t *testing.T) { +// medium := NewMockMedium() +// // How to make Write fail? +// err := Write(medium, "test.txt", "hello") +// assert.Error(t, err) +// } + +func TestIO_EnsureDir_Good(t *testing.T) { + medium := NewMockMedium() + err := EnsureDir(medium, "testdir") + assert.NoError(t, err) + exists := medium.Dirs["testdir"] + assert.True(t, exists) +} + +// TODO: The current MockMedium cannot simulate an EnsureDir error. +// func TestIO_EnsureDir_Bad(t *testing.T) { +// medium := NewMockMedium() +// // How to make EnsureDir fail? +// err := EnsureDir(medium, "testdir") +// assert.Error(t, err) +// } + +func TestIO_IsFile_Good(t *testing.T) { + medium := NewMockMedium() + medium.Files["test.txt"] = "content" + assert.True(t, IsFile(medium, "test.txt")) + assert.False(t, IsFile(medium, "nonexistent.txt")) +} + +func TestIO_Copy_Good(t *testing.T) { + source := NewMockMedium() + source.Files["source.txt"] = "hello" + + dest := NewMockMedium() + + err := Copy(source, "source.txt", dest, "dest.txt") + assert.NoError(t, err) + + copiedContent, ok := dest.Files["dest.txt"] + assert.True(t, ok) + assert.Equal(t, "hello", copiedContent) +} + +func TestIO_Copy_Bad(t *testing.T) { + source := NewMockMedium() // No source file + dest := NewMockMedium() + + err := Copy(source, "source.txt", dest, "dest.txt") + assert.Error(t, err) +} diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 6d599c4a..091bf5f6 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -1,4 +1,4 @@ -package runtime +package runtime_test import ( "errors" @@ -8,67 +8,107 @@ import ( "github.com/Snider/Core/pkg/crypt" "github.com/Snider/Core/pkg/display" "github.com/Snider/Core/pkg/help" + "github.com/Snider/Core/pkg/i18n" + "github.com/Snider/Core/pkg/runtime" "github.com/Snider/Core/pkg/workspace" "github.com/stretchr/testify/assert" + "github.com/wailsapp/wails/v3/pkg/application" ) -// TestNew ensures that New correctly initializes a Runtime instance. func TestNew(t *testing.T) { - // Pass nil for the application, as it is not required for this test. - runtime, err := New(nil) - assert.NoError(t, err) - assert.NotNil(t, runtime) - - // Assert that key services are initialized - assert.NotNil(t, runtime.Core, "Core service should be initialized") - assert.NotNil(t, runtime.Config, "Config service should be initialized") - assert.NotNil(t, runtime.Display, "Display service should be initialized") - assert.NotNil(t, runtime.Help, "Help service should be initialized") - assert.NotNil(t, runtime.Crypt, "Crypt service should be initialized") - assert.NotNil(t, runtime.I18n, "I18n service should be initialized") - assert.NotNil(t, runtime.Workspace, "Workspace service should be initialized") - - // Verify services are properly wired through Core - configFromCore := runtime.Core.Service("config") - assert.NotNil(t, configFromCore, "Config should be registered in Core") - assert.Equal(t, runtime.Config, configFromCore, "Config from Core should match direct reference") - - displayFromCore := runtime.Core.Service("display") - assert.NotNil(t, displayFromCore, "Display should be registered in Core") - assert.Equal(t, runtime.Display, displayFromCore, "Display from Core should match direct reference") - - helpFromCore := runtime.Core.Service("help") - assert.NotNil(t, helpFromCore, "Help should be registered in Core") - assert.Equal(t, runtime.Help, helpFromCore, "Help from Core should match direct reference") - - cryptFromCore := runtime.Core.Service("crypt") - assert.NotNil(t, cryptFromCore, "Crypt should be registered in Core") - assert.Equal(t, runtime.Crypt, cryptFromCore, "Crypt from Core should match direct reference") - - i18nFromCore := runtime.Core.Service("i18n") - assert.NotNil(t, i18nFromCore, "I18n should be registered in Core") - assert.Equal(t, runtime.I18n, i18nFromCore, "I18n from Core should match direct reference") - - workspaceFromCore := runtime.Core.Service("workspace") - assert.NotNil(t, workspaceFromCore, "Workspace should be registered in Core") - assert.Equal(t, runtime.Workspace, workspaceFromCore, "Workspace from Core should match direct reference") -} - -// TestNewServiceInitializationError tests the error path in New. -func TestNewServiceInitializationError(t *testing.T) { - factories := map[string]ServiceFactory{ - "config": func() (any, error) { return config.New() }, - "display": func() (any, error) { return display.New() }, - "help": func() (any, error) { return help.New() }, - "crypt": func() (any, error) { return crypt.New() }, - "i18n": func() (any, error) { return nil, errors.New("i18n service failed to initialize") }, // This factory will fail - "workspace": func() (any, error) { return workspace.New() }, + testCases := []struct { + name string + app *application.App + factories map[string]runtime.ServiceFactory + expectErr bool + expectErrStr string + checkRuntime func(*testing.T, *runtime.Runtime) + }{ + { + name: "Good path", + app: nil, + factories: map[string]runtime.ServiceFactory{ + "config": func() (any, error) { return &config.Service{}, nil }, + "display": func() (any, error) { return &display.Service{}, nil }, + "help": func() (any, error) { return &help.Service{}, nil }, + "crypt": func() (any, error) { return &crypt.Service{}, nil }, + "i18n": func() (any, error) { return &i18n.Service{}, nil }, + "workspace": func() (any, error) { return &workspace.Service{}, nil }, + }, + expectErr: false, + checkRuntime: func(t *testing.T, rt *runtime.Runtime) { + assert.NotNil(t, rt) + assert.NotNil(t, rt.Core) + assert.NotNil(t, rt.Config) + assert.NotNil(t, rt.Display) + assert.NotNil(t, rt.Help) + assert.NotNil(t, rt.Crypt) + assert.NotNil(t, rt.I18n) + assert.NotNil(t, rt.Workspace) + }, + }, + { + name: "Factory returns an error", + app: nil, + factories: map[string]runtime.ServiceFactory{ + "config": func() (any, error) { return &config.Service{}, nil }, + "display": func() (any, error) { return &display.Service{}, nil }, + "help": func() (any, error) { return &help.Service{}, nil }, + "crypt": func() (any, error) { return nil, errors.New("crypt service failed") }, + "i18n": func() (any, error) { return &i18n.Service{}, nil }, + "workspace": func() (any, error) { return &workspace.Service{}, nil }, + }, + expectErr: true, + expectErrStr: "failed to create service crypt: crypt service failed", + }, + { + name: "Factory returns wrong type", + app: nil, + factories: map[string]runtime.ServiceFactory{ + "config": func() (any, error) { return &config.Service{}, nil }, + "display": func() (any, error) { return "not a display service", nil }, + "help": func() (any, error) { return &help.Service{}, nil }, + "crypt": func() (any, error) { return &crypt.Service{}, nil }, + "i18n": func() (any, error) { return &i18n.Service{}, nil }, + "workspace": func() (any, error) { return &workspace.Service{}, nil }, + }, + expectErr: true, + expectErrStr: "display service has unexpected type", + }, + { + name: "With non-nil app", + app: &application.App{}, + factories: map[string]runtime.ServiceFactory{ + "config": func() (any, error) { return &config.Service{}, nil }, + "display": func() (any, error) { return &display.Service{}, nil }, + "help": func() (any, error) { return &help.Service{}, nil }, + "crypt": func() (any, error) { return &crypt.Service{}, nil }, + "i18n": func() (any, error) { return &i18n.Service{}, nil }, + "workspace": func() (any, error) { return &workspace.Service{}, nil }, + }, + expectErr: false, + checkRuntime: func(t *testing.T, rt *runtime.Runtime) { + assert.NotNil(t, rt) + assert.NotNil(t, rt.Core) + assert.NotNil(t, rt.Core.App) + }, + }, } - // Pass nil for the application, as it is not required for this test. - runtime, err := NewWithFactories(nil, factories) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rt, err := runtime.NewWithFactories(tc.app, tc.factories) - assert.Error(t, err) - assert.Nil(t, runtime) - assert.Contains(t, err.Error(), "failed to create service i18n: i18n service failed to initialize") + if tc.expectErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expectErrStr) + assert.Nil(t, rt) + } else { + assert.NoError(t, err) + if tc.checkRuntime != nil { + tc.checkRuntime(t, rt) + } + } + }) + } } diff --git a/tdd/core_test.go b/tdd/core_test.go deleted file mode 100644 index 0d4e89d3..00000000 --- a/tdd/core_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package tdd - -import ( - "embed" - "io" - "testing" - - "github.com/Snider/Core/pkg/core" - "github.com/stretchr/testify/assert" - "github.com/wailsapp/wails/v3/pkg/application" -) - -func TestCore_New_Good(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - assert.NotNil(t, c) -} - -// Mock service for testing -type MockService struct { - Name string -} - -func (m *MockService) GetName() string { - return m.Name -} - -func TestCore_WithService_Good(t *testing.T) { - factory := func(c *core.Core) (any, error) { - return &MockService{Name: "test"}, nil - } - c, err := core.New(core.WithService(factory)) - assert.NoError(t, err) - svc := c.Service("tdd") - assert.NotNil(t, svc) - mockSvc, ok := svc.(*MockService) - assert.True(t, ok) - assert.Equal(t, "test", mockSvc.GetName()) -} - -func TestCore_WithService_Bad(t *testing.T) { - factory := func(c *core.Core) (any, error) { - return nil, assert.AnError - } - _, err := core.New(core.WithService(factory)) - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) -} - -func TestCore_WithWails_Good(t *testing.T) { - app := &application.App{} - c, err := core.New(core.WithWails(app)) - assert.NoError(t, err) - assert.Equal(t, app, c.App) -} - -//go:embed testdata -var testFS embed.FS - -func TestCore_WithAssets_Good(t *testing.T) { - c, err := core.New(core.WithAssets(testFS)) - assert.NoError(t, err) - assets := c.Assets() - file, err := assets.Open("testdata/test.txt") - assert.NoError(t, err) - defer file.Close() - content, err := io.ReadAll(file) - assert.NoError(t, err) - assert.Equal(t, "hello from testdata\n", string(content)) -} - -func TestCore_WithServiceLock_Good(t *testing.T) { - c, err := core.New(core.WithServiceLock()) - assert.NoError(t, err) - err = c.RegisterService("test", &MockService{}) - assert.Error(t, err) -} - -func TestCore_RegisterService_Good(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - err = c.RegisterService("test", &MockService{Name: "test"}) - assert.NoError(t, err) - svc := c.Service("test") - assert.NotNil(t, svc) - mockSvc, ok := svc.(*MockService) - assert.True(t, ok) - assert.Equal(t, "test", mockSvc.GetName()) -} - -func TestCore_RegisterService_Bad(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - err = c.RegisterService("test", &MockService{}) - assert.NoError(t, err) - err = c.RegisterService("test", &MockService{}) - assert.Error(t, err) - err = c.RegisterService("", &MockService{}) - assert.Error(t, err) -} - -func TestCore_ServiceFor_Good(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - err = c.RegisterService("test", &MockService{Name: "test"}) - assert.NoError(t, err) - svc, err := core.ServiceFor[*MockService](c, "test") - assert.NoError(t, err) - assert.Equal(t, "test", svc.GetName()) -} - -func TestCore_ServiceFor_Bad(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - _, err = core.ServiceFor[*MockService](c, "nonexistent") - assert.Error(t, err) - err = c.RegisterService("test", "not a service") - assert.NoError(t, err) - _, err = core.ServiceFor[*MockService](c, "test") - assert.Error(t, err) -} - -func TestCore_MustServiceFor_Good(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - err = c.RegisterService("test", &MockService{Name: "test"}) - assert.NoError(t, err) - svc := core.MustServiceFor[*MockService](c, "test") - assert.Equal(t, "test", svc.GetName()) -} - -func TestCore_MustServiceFor_Ugly(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - assert.Panics(t, func() { - core.MustServiceFor[*MockService](c, "nonexistent") - }) - err = c.RegisterService("test", "not a service") - assert.NoError(t, err) - assert.Panics(t, func() { - core.MustServiceFor[*MockService](c, "test") - }) -} - -type MockAction struct { - handled bool -} - -func (a *MockAction) Handle(c *core.Core, msg core.Message) error { - a.handled = true - return nil -} - -func TestCore_ACTION_Good(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - action := &MockAction{} - c.RegisterAction(action.Handle) - err = c.ACTION(nil) - assert.NoError(t, err) - assert.True(t, action.handled) -} - -func TestCore_RegisterActions_Good(t *testing.T) { - c, err := core.New() - assert.NoError(t, err) - action1 := &MockAction{} - action2 := &MockAction{} - c.RegisterActions(action1.Handle, action2.Handle) - err = c.ACTION(nil) - assert.NoError(t, err) - assert.True(t, action1.handled) - assert.True(t, action2.handled) -} - -func TestCore_WithName_Good(t *testing.T) { - factory := func(c *core.Core) (any, error) { - return &MockService{Name: "test"}, nil - } - c, err := core.New(core.WithName("my-service", factory)) - assert.NoError(t, err) - svc := c.Service("my-service") - assert.NotNil(t, svc) - mockSvc, ok := svc.(*MockService) - assert.True(t, ok) - assert.Equal(t, "test", mockSvc.GetName()) -} - -func TestCore_WithName_Bad(t *testing.T) { - factory := func(c *core.Core) (any, error) { - return nil, assert.AnError - } - _, err := core.New(core.WithName("my-service", factory)) - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) -} diff --git a/tdd/io_test.go b/tdd/io_test.go deleted file mode 100644 index 8190aa2a..00000000 --- a/tdd/io_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package tdd - -import ( - "errors" - "testing" - - "github.com/Snider/Core/pkg/io" - "github.com/stretchr/testify/assert" -) - -type MockMedium struct { - ReadFileFunc func(path string) (string, error) - WriteFileFunc func(path, content string) error - EnsureDirFunc func(path string) error - IsFileFunc func(path string) bool -} - -func (m *MockMedium) Read(path string) (string, error) { - if m.ReadFileFunc != nil { - return m.ReadFileFunc(path) - } - return "", errors.New("not implemented") -} - -func (m *MockMedium) Write(path, content string) error { - if m.WriteFileFunc != nil { - return m.WriteFileFunc(path, content) - } - return errors.New("not implemented") -} - -func (m *MockMedium) EnsureDir(path string) error { - if m.EnsureDirFunc != nil { - return m.EnsureDirFunc(path) - } - return errors.New("not implemented") -} - -func (m *MockMedium) IsFile(path string) bool { - if m.IsFileFunc != nil { - return m.IsFileFunc(path) - } - return false -} - -func (m *MockMedium) FileGet(path string) (string, error) { - return m.Read(path) -} - -func (m *MockMedium) FileSet(path, content string) error { - return m.Write(path, content) -} - -func TestIO_Read_Good(t *testing.T) { - medium := &MockMedium{ - ReadFileFunc: func(path string) (string, error) { - assert.Equal(t, "test.txt", path) - return "hello", nil - }, - } - content, err := io.Read(medium, "test.txt") - assert.NoError(t, err) - assert.Equal(t, "hello", content) -} - -func TestIO_Read_Bad(t *testing.T) { - medium := &MockMedium{ - ReadFileFunc: func(path string) (string, error) { - return "", assert.AnError - }, - } - _, err := io.Read(medium, "test.txt") - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) -} - -func TestIO_Write_Good(t *testing.T) { - medium := &MockMedium{ - WriteFileFunc: func(path, content string) error { - assert.Equal(t, "test.txt", path) - assert.Equal(t, "hello", content) - return nil - }, - } - err := io.Write(medium, "test.txt", "hello") - assert.NoError(t, err) -} - -func TestIO_Write_Bad(t *testing.T) { - medium := &MockMedium{ - WriteFileFunc: func(path, content string) error { - return assert.AnError - }, - } - err := io.Write(medium, "test.txt", "hello") - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) -} - -func TestIO_EnsureDir_Good(t *testing.T) { - medium := &MockMedium{ - EnsureDirFunc: func(path string) error { - assert.Equal(t, "testdir", path) - return nil - }, - } - err := io.EnsureDir(medium, "testdir") - assert.NoError(t, err) -} - -func TestIO_EnsureDir_Bad(t *testing.T) { - medium := &MockMedium{ - EnsureDirFunc: func(path string) error { - return assert.AnError - }, - } - err := io.EnsureDir(medium, "testdir") - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) -} - -func TestIO_IsFile_Good(t *testing.T) { - medium := &MockMedium{ - IsFileFunc: func(path string) bool { - assert.Equal(t, "test.txt", path) - return true - }, - } - assert.True(t, io.IsFile(medium, "test.txt")) -} - -func TestIO_Copy_Good(t *testing.T) { - source := &MockMedium{ - ReadFileFunc: func(path string) (string, error) { - assert.Equal(t, "source.txt", path) - return "hello", nil - }, - } - dest := &MockMedium{ - WriteFileFunc: func(path, content string) error { - assert.Equal(t, "dest.txt", path) - assert.Equal(t, "hello", content) - return nil - }, - } - err := io.Copy(source, "source.txt", dest, "dest.txt") - assert.NoError(t, err) -} - -func TestIO_Copy_Bad(t *testing.T) { - source := &MockMedium{ - ReadFileFunc: func(path string) (string, error) { - return "", assert.AnError - }, - } - dest := &MockMedium{} - err := io.Copy(source, "source.txt", dest, "dest.txt") - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) -} diff --git a/tdd/runtime_test.go b/tdd/runtime_test.go deleted file mode 100644 index 085a6655..00000000 --- a/tdd/runtime_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package tdd - -import ( - "errors" - "testing" - - "github.com/Snider/Core/pkg/config" - "github.com/Snider/Core/pkg/crypt" - "github.com/Snider/Core/pkg/display" - "github.com/Snider/Core/pkg/help" - "github.com/Snider/Core/pkg/i18n" - "github.com/Snider/Core/pkg/runtime" - "github.com/Snider/Core/pkg/workspace" - "github.com/stretchr/testify/assert" - "github.com/wailsapp/wails/v3/pkg/application" -) - -func TestNew(t *testing.T) { - testCases := []struct { - name string - app *application.App - factories map[string]runtime.ServiceFactory - expectErr bool - expectErrStr string - checkRuntime func(*testing.T, *runtime.Runtime) - }{ - { - name: "Good path", - app: nil, - factories: map[string]runtime.ServiceFactory{ - "config": func() (any, error) { return &config.Service{}, nil }, - "display": func() (any, error) { return &display.Service{}, nil }, - "help": func() (any, error) { return &help.Service{}, nil }, - "crypt": func() (any, error) { return &crypt.Service{}, nil }, - "i18n": func() (any, error) { return &i18n.Service{}, nil }, - "workspace": func() (any, error) { return &workspace.Service{}, nil }, - }, - expectErr: false, - checkRuntime: func(t *testing.T, rt *runtime.Runtime) { - assert.NotNil(t, rt) - assert.NotNil(t, rt.Core) - assert.NotNil(t, rt.Config) - assert.NotNil(t, rt.Display) - assert.NotNil(t, rt.Help) - assert.NotNil(t, rt.Crypt) - assert.NotNil(t, rt.I18n) - assert.NotNil(t, rt.Workspace) - }, - }, - { - name: "Factory returns an error", - app: nil, - factories: map[string]runtime.ServiceFactory{ - "config": func() (any, error) { return &config.Service{}, nil }, - "display": func() (any, error) { return &display.Service{}, nil }, - "help": func() (any, error) { return &help.Service{}, nil }, - "crypt": func() (any, error) { return nil, errors.New("crypt service failed") }, - "i18n": func() (any, error) { return &i18n.Service{}, nil }, - "workspace": func() (any, error) { return &workspace.Service{}, nil }, - }, - expectErr: true, - expectErrStr: "failed to create service crypt: crypt service failed", - }, - { - name: "Factory returns wrong type", - app: nil, - factories: map[string]runtime.ServiceFactory{ - "config": func() (any, error) { return &config.Service{}, nil }, - "display": func() (any, error) { return "not a display service", nil }, - "help": func() (any, error) { return &help.Service{}, nil }, - "crypt": func() (any, error) { return &crypt.Service{}, nil }, - "i18n": func() (any, error) { return &i18n.Service{}, nil }, - "workspace": func() (any, error) { return &workspace.Service{}, nil }, - }, - expectErr: true, - expectErrStr: "display service has unexpected type", - }, - { - name: "With non-nil app", - app: &application.App{}, - factories: map[string]runtime.ServiceFactory{ - "config": func() (any, error) { return &config.Service{}, nil }, - "display": func() (any, error) { return &display.Service{}, nil }, - "help": func() (any, error) { return &help.Service{}, nil }, - "crypt": func() (any, error) { return &crypt.Service{}, nil }, - "i18n": func() (any, error) { return &i18n.Service{}, nil }, - "workspace": func() (any, error) { return &workspace.Service{}, nil }, - }, - expectErr: false, - checkRuntime: func(t *testing.T, rt *runtime.Runtime) { - assert.NotNil(t, rt) - assert.NotNil(t, rt.Core) - assert.NotNil(t, rt.Core.App) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - rt, err := runtime.NewWithFactories(tc.app, tc.factories) - - if tc.expectErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), tc.expectErrStr) - assert.Nil(t, rt) - } else { - assert.NoError(t, err) - if tc.checkRuntime != nil { - tc.checkRuntime(t, rt) - } - } - }) - } -}