1
1
package api
2
2
3
3
import (
4
+ "bufio"
4
5
"bytes"
5
6
"encoding/json"
6
7
"errors"
8
+ "fmt"
7
9
"io"
8
10
"net/http"
9
11
"os"
12
+ "regexp"
10
13
"sort"
11
14
"strconv"
12
15
"strings"
@@ -53,9 +56,15 @@ const (
53
56
AppAnalysisIssuesRoot = AppAnalysisRoot + "/issues"
54
57
)
55
58
59
+ // Manifest markers.
60
+ // The GS=\x1D (group separator).
56
61
const (
57
- IssueField = "issues"
58
- DepField = "dependencies"
62
+ BeginMainMarker = "\x1D BEGIN-MAIN\x1D "
63
+ EndMainMarker = "\x1D END-MAIN\x1D "
64
+ BeginIssuesMarker = "\x1D BEGIN-ISSUES\x1D "
65
+ EndIssuesMarker = "\x1D END-ISSUES\x1D "
66
+ BeginDepsMarker = "\x1D BEGIN-DEPS\x1D "
67
+ EndDepsMarker = "\x1D END-DEPS\x1D "
59
68
)
60
69
61
70
// AnalysisHandler handles analysis resource routes.
@@ -315,9 +324,20 @@ func (h AnalysisHandler) AppList(ctx *gin.Context) {
315
324
// @summary Create an analysis.
316
325
// @description Create an analysis.
317
326
// @description Form fields:
318
- // @description - file: file that contains the api.Analysis resource.
319
- // @description - issues: file that multiple api.Issue resources.
320
- // @description - dependencies: file that multiple api.TechDependency resources.
327
+ // @description file: A manifest file that contains 3 sections
328
+ // @description containing documents delimited by markers.
329
+ // @description The manifest must contain ALL markers even when sections are empty.
330
+ // @description Note: `^]` = `\x1D` = GS (group separator).
331
+ // @description Section markers:
332
+ // @description ^]BEGIN-MAIN^]
333
+ // @description ^]END-MAIN^]
334
+ // @description ^]BEGIN-ISSUES^]
335
+ // @description ^]END-ISSUES^]
336
+ // @description ^]BEGIN-DEPS^]
337
+ // @description ^]END-DEPS^]
338
+ // @description The encoding must be:
339
+ // @description - application/json
340
+ // @description - application/x-yaml
321
341
// @tags analyses
322
342
// @produce json
323
343
// @success 201 {object} api.Analysis
@@ -337,32 +357,40 @@ func (h AnalysisHandler) AppCreate(ctx *gin.Context) {
337
357
return
338
358
}
339
359
}
360
+ db := h .DB (ctx )
340
361
//
341
- // Analysis
342
- input , err := ctx .FormFile (FileField )
362
+ // Manifest
363
+ fh := FileHandler {}
364
+ name := fmt .Sprintf ("app.%d.manifest" , id )
365
+ file , err := fh .create (ctx , name )
343
366
if err != nil {
344
- err = & BadRequestError {err .Error ()}
345
367
_ = ctx .Error (err )
346
368
return
347
369
}
348
- reader , err := input .Open ()
370
+ defer func () {
371
+ err = fh .delete (ctx , file )
372
+ if err != nil {
373
+ _ = ctx .Error (err )
374
+ }
375
+ }()
376
+ reader := & ManifestReader {}
377
+ f , err := reader .open (file .Path , BeginMainMarker , EndMainMarker )
349
378
if err != nil {
350
379
err = & BadRequestError {err .Error ()}
351
380
_ = ctx .Error (err )
352
381
return
353
382
}
354
383
defer func () {
355
- _ = reader .Close ()
384
+ _ = f .Close ()
356
385
}()
357
- encoding := input .Header .Get (ContentType )
358
- d , err := h .Decoder (ctx , encoding , reader )
386
+ d , err := h .Decoder (ctx , file .Encoding , reader )
359
387
if err != nil {
360
388
err = & BadRequestError {err .Error ()}
361
389
_ = ctx .Error (err )
362
390
return
363
391
}
364
- r := Analysis {}
365
- err = d .Decode (& r )
392
+ r := & Analysis {}
393
+ err = d .Decode (r )
366
394
if err != nil {
367
395
err = & BadRequestError {err .Error ()}
368
396
_ = ctx .Error (err )
@@ -371,7 +399,6 @@ func (h AnalysisHandler) AppCreate(ctx *gin.Context) {
371
399
analysis := r .Model ()
372
400
analysis .ApplicationID = id
373
401
analysis .CreateUser = h .BaseHandler .CurrentUser (ctx )
374
- db := h .DB (ctx )
375
402
db .Logger = db .Logger .LogMode (logger .Error )
376
403
err = db .Create (analysis ).Error
377
404
if err != nil {
@@ -380,23 +407,17 @@ func (h AnalysisHandler) AppCreate(ctx *gin.Context) {
380
407
}
381
408
//
382
409
// Issues
383
- input , err = ctx .FormFile (IssueField )
384
- if err != nil {
385
- err = & BadRequestError {err .Error ()}
386
- _ = ctx .Error (err )
387
- return
388
- }
389
- reader , err = input .Open ()
410
+ reader = & ManifestReader {}
411
+ f , err = reader .open (file .Path , BeginIssuesMarker , EndIssuesMarker )
390
412
if err != nil {
391
413
err = & BadRequestError {err .Error ()}
392
414
_ = ctx .Error (err )
393
415
return
394
416
}
395
417
defer func () {
396
- _ = reader .Close ()
418
+ _ = f .Close ()
397
419
}()
398
- encoding = input .Header .Get (ContentType )
399
- d , err = h .Decoder (ctx , encoding , reader )
420
+ d , err = h .Decoder (ctx , file .Encoding , reader )
400
421
if err != nil {
401
422
err = & BadRequestError {err .Error ()}
402
423
_ = ctx .Error (err )
@@ -425,23 +446,17 @@ func (h AnalysisHandler) AppCreate(ctx *gin.Context) {
425
446
}
426
447
//
427
448
// Dependencies
428
- input , err = ctx .FormFile (DepField )
429
- if err != nil {
430
- err = & BadRequestError {err .Error ()}
431
- _ = ctx .Error (err )
432
- return
433
- }
434
- reader , err = input .Open ()
449
+ reader = & ManifestReader {}
450
+ f , err = reader .open (file .Path , BeginDepsMarker , EndDepsMarker )
435
451
if err != nil {
436
452
err = & BadRequestError {err .Error ()}
437
453
_ = ctx .Error (err )
438
454
return
439
455
}
440
456
defer func () {
441
- _ = reader .Close ()
457
+ _ = f .Close ()
442
458
}()
443
- encoding = input .Header .Get (ContentType )
444
- d , err = h .Decoder (ctx , encoding , reader )
459
+ d , err = h .Decoder (ctx , file .Encoding , reader )
445
460
if err != nil {
446
461
err = & BadRequestError {err .Error ()}
447
462
_ = ctx .Error (err )
@@ -2860,3 +2875,116 @@ func (r *yamlEncoder) embed(object any) encoder {
2860
2875
r .write (s )
2861
2876
return r
2862
2877
}
2878
+
2879
+ // ManifestReader analysis manifest reader.
2880
+ // The manifest contains 3 sections containing documents delimited by markers.
2881
+ // The manifest must contain ALL markers even when sections are empty.
2882
+ // Note: `^]` = `\x1D` = GS (group separator).
2883
+ // Section markers:
2884
+ //
2885
+ // ^]BEGIN-MAIN^]
2886
+ // ^]END-MAIN^]
2887
+ // ^]BEGIN-ISSUES^]
2888
+ // ^]END-ISSUES^]
2889
+ // ^]BEGIN-DEPS^]
2890
+ // ^]END-DEPS^]
2891
+ type ManifestReader struct {
2892
+ file * os.File
2893
+ marker map [string ]int64
2894
+ begin int64
2895
+ end int64
2896
+ read int64
2897
+ }
2898
+
2899
+ // scan manifest and catalog position of markers.
2900
+ func (r * ManifestReader ) scan (path string ) (err error ) {
2901
+ if r .marker != nil {
2902
+ return
2903
+ }
2904
+ r .file , err = os .Open (path )
2905
+ if err != nil {
2906
+ return
2907
+ }
2908
+ defer func () {
2909
+ _ = r .file .Close ()
2910
+ }()
2911
+ pattern , err := regexp .Compile (`^\x1D[A-Z-]+\x1D$` )
2912
+ if err != nil {
2913
+ return
2914
+ }
2915
+ p := int64 (0 )
2916
+ r .marker = make (map [string ]int64 )
2917
+ scanner := bufio .NewScanner (r .file )
2918
+ for scanner .Scan () {
2919
+ content := scanner .Text ()
2920
+ matched := strings .TrimSpace (content )
2921
+ if pattern .Match ([]byte (matched )) {
2922
+ r .marker [matched ] = p
2923
+ }
2924
+ p += int64 (len (content ))
2925
+ p ++
2926
+ }
2927
+
2928
+ return
2929
+ }
2930
+
2931
+ // open returns a read delimited by the specified markers.
2932
+ func (r * ManifestReader ) open (path , begin , end string ) (reader io.ReadCloser , err error ) {
2933
+ found := false
2934
+ err = r .scan (path )
2935
+ if err != nil {
2936
+ return
2937
+ }
2938
+ r .begin , found = r .marker [begin ]
2939
+ if ! found {
2940
+ err = & BadRequestError {
2941
+ Reason : fmt .Sprintf ("marker: %s not found." , begin ),
2942
+ }
2943
+ return
2944
+ }
2945
+ r .end , found = r .marker [end ]
2946
+ if ! found {
2947
+ err = & BadRequestError {
2948
+ Reason : fmt .Sprintf ("marker: %s not found." , end ),
2949
+ }
2950
+ return
2951
+ }
2952
+ if r .begin >= r .end {
2953
+ err = & BadRequestError {
2954
+ Reason : fmt .Sprintf ("marker: %s must preceed %s." , begin , end ),
2955
+ }
2956
+ return
2957
+ }
2958
+ r .begin += int64 (len (begin ))
2959
+ r .begin ++
2960
+ r .read = r .end - r .begin
2961
+ r .file , err = os .Open (path )
2962
+ if err != nil {
2963
+ return
2964
+ }
2965
+ _ , err = r .file .Seek (r .begin , io .SeekStart )
2966
+ reader = r
2967
+ return
2968
+ }
2969
+
2970
+ // Read bytes.
2971
+ func (r * ManifestReader ) Read (b []byte ) (n int , err error ) {
2972
+ n , err = r .file .Read (b )
2973
+ if n == 0 || err != nil {
2974
+ return
2975
+ }
2976
+ if int64 (n ) > r .read {
2977
+ n = int (r .read )
2978
+ }
2979
+ r .read -= int64 (n )
2980
+ if n < 1 {
2981
+ err = io .EOF
2982
+ }
2983
+ return
2984
+ }
2985
+
2986
+ // Close the reader.
2987
+ func (r * ManifestReader ) Close () (err error ) {
2988
+ err = r .file .Close ()
2989
+ return
2990
+ }
0 commit comments