Skip to content

Commit 59be994

Browse files
committed
simplify array handling and add json converter for complex types
1 parent 445dd29 commit 59be994

3 files changed

Lines changed: 134 additions & 52 deletions

File tree

src/ARCtrl/Conversion/Workflow.fs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,33 @@ open ARCtrl.Helper.Regex.ActivePatterns
1414
type WorkflowConversion =
1515

1616
static member composeAdditionalType (t : CWL.CWLType) : string =
17-
CWL.Encode.encodeCWLType t
18-
|> CWL.Encode.writeYaml
19-
|> fun s -> s.Trim()
17+
// Check if this is a complex type that needs JSON encoding
18+
let isComplexType =
19+
match t with
20+
| CWL.Record _ | CWL.Enum _ -> true
21+
| CWL.Array arraySchema ->
22+
// Complex if array doesn't have shorthand (array of record/enum)
23+
CWL.Encode.tryGetArrayShorthand arraySchema.Items |> Option.isNone
24+
| CWL.Union types ->
25+
// Complex if not a simple optional type
26+
let typesList = types |> Seq.toList
27+
match typesList with
28+
| [CWL.Null; otherType] | [otherType; CWL.Null] ->
29+
match otherType with
30+
| CWL.Record _ | CWL.Enum _ -> true
31+
| CWL.Array arraySchema ->
32+
CWL.Encode.tryGetArrayShorthand arraySchema.Items |> Option.isNone
33+
| _ -> false
34+
| _ -> true // Multi-type union is complex
35+
| _ -> false
36+
37+
// Use JSON format for complex types, YAML shorthand for simple types
38+
if isComplexType then
39+
CWL.Encode.cwlTypeToJsonString t
40+
else
41+
CWL.Encode.encodeCWLType t
42+
|> CWL.Encode.writeYaml
43+
|> fun s -> s.Trim()
2044

2145
static member decomposeAdditionalType (t : string) : CWL.CWLType =
2246
YAMLicious.Decode.object (fun get ->

src/CWL/Decode.fs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ module Decode =
8686
| "null" -> Null
8787
| _ -> failwith $"Invalid CWL simple type: {s}"
8888

89+
/// Recursively parse array shorthand notation (File[][], string[][][], etc.)
90+
let rec parseArrayShorthand (typeStr: string) : CWLType option =
91+
if typeStr.EndsWith("[]") then
92+
let innerType = typeStr.Substring(0, typeStr.Length - 2)
93+
// Try to parse the inner type recursively
94+
match parseArrayShorthand innerType with
95+
| Some innerCwlType ->
96+
// Nested array
97+
Some (Array { Items = innerCwlType; Label = None; Doc = None; Name = None })
98+
| None ->
99+
// Base type with array suffix
100+
try
101+
let baseType = cwlSimpleTypeFromString innerType
102+
Some (Array { Items = baseType; Label = None; Doc = None; Name = None })
103+
with _ -> None
104+
else
105+
None
106+
89107
/// Decode an InputArraySchema from a YAMLElement
90108
and inputArraySchemaDecoder: (YAMLiciousTypes.YAMLElement -> InputArraySchema) =
91109
Decode.object (fun get ->
@@ -173,13 +191,11 @@ module Decode =
173191
else
174192
typeStr, false
175193

176-
// Handle array suffix
194+
// Try to parse as array shorthand (handles arbitrary nesting recursively)
177195
let baseType =
178-
if stripped.EndsWith("[]") then
179-
let baseType = stripped.Replace("[]", "")
180-
Array { Items = cwlSimpleTypeFromString baseType; Label = None; Doc = None; Name = None }
181-
else
182-
cwlSimpleTypeFromString stripped
196+
match parseArrayShorthand stripped with
197+
| Some arrayType -> arrayType
198+
| None -> cwlSimpleTypeFromString stripped
183199

184200
// Wrap in Union if optional
185201
if isOptional then
@@ -219,23 +235,13 @@ module Decode =
219235
true, t.Replace("?", "")
220236
else
221237
false, t
238+
239+
// Try to parse as array shorthand (handles arbitrary nesting recursively)
222240
let cwlType =
223-
if newT.EndsWith("[]") then
224-
let baseType = newT.Replace("[]", "")
225-
let baseItem =
226-
match baseType with
227-
| "File" -> File (FileInstance ())
228-
| "Directory" -> Directory (DirectoryInstance ())
229-
| "Dirent" -> (get.Required.Field "listing" direntDecoder)
230-
| "string" -> String
231-
| "int" -> Int
232-
| "long" -> Long
233-
| "float" -> Float
234-
| "double" -> Double
235-
| "boolean" -> Boolean
236-
| _ -> failwith "Invalid CWL type"
237-
Array { Items = baseItem; Label = None; Doc = None; Name = None }
238-
else
241+
match parseArrayShorthand newT with
242+
| Some arrayType -> arrayType
243+
| None ->
244+
// Not an array, check for simple types or Dirent
239245
match newT with
240246
| "File" -> File (FileInstance ())
241247
| "Directory" -> Directory (DirectoryInstance ())
@@ -249,6 +255,7 @@ module Decode =
249255
| "stdout" -> Stdout
250256
| "null" -> Null
251257
| _ -> failwith "Invalid CWL type"
258+
252259
// Wrap in Union if optional
253260
let finalType =
254261
if optional then

src/CWL/Encode.fs

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ module Encode =
5151
| Some pair -> acc @ [pair]
5252
| None -> acc
5353

54+
// ------------------------------
55+
// Helper for recursive array shorthand detection
56+
// ------------------------------
57+
let rec tryGetArrayShorthand (cwlType: CWLType) : string option =
58+
match cwlType with
59+
| File _ -> Some "File"
60+
| Directory _ -> Some "Directory"
61+
| Dirent _ -> Some "Dirent"
62+
| String -> Some "string"
63+
| Int -> Some "int"
64+
| Long -> Some "long"
65+
| Float -> Some "float"
66+
| Double -> Some "double"
67+
| Boolean -> Some "boolean"
68+
| Array innerSchema ->
69+
// Recursively get shorthand for inner type and append []
70+
tryGetArrayShorthand innerSchema.Items |> Option.map (fun s -> s + "[]")
71+
| _ -> None
72+
5473
// ------------------------------
5574
// CWLType encoder
5675
// ------------------------------
@@ -87,17 +106,10 @@ module Encode =
87106
| Double -> Encode.string "double?"
88107
| Boolean -> Encode.string "boolean?"
89108
| Array arraySchema ->
90-
// Optional array - check if items are simple
91-
match arraySchema.Items with
92-
| File _ -> Encode.string "File[]?"
93-
| Directory _ -> Encode.string "Directory[]?"
94-
| String -> Encode.string "string[]?"
95-
| Int -> Encode.string "int[]?"
96-
| Long -> Encode.string "long[]?"
97-
| Float -> Encode.string "float[]?"
98-
| Double -> Encode.string "double[]?"
99-
| Boolean -> Encode.string "boolean[]?"
100-
| _ ->
109+
// Optional array - use recursive shorthand detection
110+
match tryGetArrayShorthand arraySchema.Items with
111+
| Some shorthand -> Encode.string (shorthand + "[]?")
112+
| None ->
101113
// Complex optional array - use full form
102114
YAMLElement.Sequence [ Encode.string "null"; encodeInputArraySchema arraySchema ]
103115
| _ ->
@@ -107,21 +119,9 @@ module Encode =
107119
// General union - use array form
108120
typesList |> List.map encodeCWLType |> YAMLElement.Sequence
109121
| Array arraySchema ->
110-
// Try to use short form for simple arrays
111-
let shortForm =
112-
match arraySchema.Items with
113-
| File _ -> Some "File[]"
114-
| Directory _ -> Some "Directory[]"
115-
| Dirent _ -> Some "Dirent[]"
116-
| String -> Some "string[]"
117-
| Int -> Some "int[]"
118-
| Long -> Some "long[]"
119-
| Float -> Some "float[]"
120-
| Double -> Some "double[]"
121-
| Boolean -> Some "boolean[]"
122-
| _ -> None
123-
match shortForm with
124-
| Some s -> Encode.string s
122+
// Try to use short form for arrays (handles arbitrary nesting depth recursively)
123+
match tryGetArrayShorthand arraySchema.Items with
124+
| Some shorthand -> Encode.string (shorthand + "[]")
125125
| None -> encodeInputArraySchema arraySchema
126126
| Record recordSchema -> encodeInputRecordSchema recordSchema
127127
| Enum enumSchema -> encodeInputEnumSchema enumSchema
@@ -504,4 +504,55 @@ module Encode =
504504
let encodeProcessingUnit (pu : CWLProcessingUnit) :string =
505505
match pu with
506506
| CommandLineTool td -> encodeToolDescription td
507-
| Workflow wd -> encodeWorkflowDescription wd
507+
| Workflow wd -> encodeWorkflowDescription wd
508+
509+
/// Escape a string for JSON
510+
let private jsonEscapeString (s: string) =
511+
s.Replace("\\", "\\\\")
512+
.Replace("\"", "\\\"")
513+
.Replace("\n", "\\n")
514+
.Replace("\r", "\\r")
515+
.Replace("\t", "\\t")
516+
517+
/// Encode a CWLType to a JSON string
518+
/// Note: This is only called for complex types without shorthand notation
519+
let rec encodeCWLTypeJson (t: CWLType) : string =
520+
match t with
521+
| Union types ->
522+
// Union of complex types - use array form
523+
let encodedTypes = types |> Seq.toList |> List.map encodeCWLTypeJson
524+
"[" + String.concat "," encodedTypes + "]"
525+
| Array arraySchema ->
526+
// Array of complex type
527+
encodeInputArraySchemaJson arraySchema
528+
| Record recordSchema -> encodeInputRecordSchemaJson recordSchema
529+
| Enum enumSchema -> encodeInputEnumSchemaJson enumSchema
530+
| _ ->
531+
// Fallback for any simple type (shouldn't be reached, but include for safety)
532+
failwith "encodeCWLTypeJson should only be called for complex types"
533+
534+
and encodeInputRecordFieldJson (field: InputRecordField) : string =
535+
sprintf "\"%s\":{\"type\":%s}" (jsonEscapeString field.Name) (encodeCWLTypeJson field.Type)
536+
537+
and encodeInputRecordSchemaJson (schema: InputRecordSchema) : string =
538+
let fieldsJson =
539+
match schema.Fields with
540+
| Some fs ->
541+
fs |> Seq.map encodeInputRecordFieldJson |> String.concat ","
542+
| None -> ""
543+
544+
sprintf "{\"type\":\"record\",\"fields\":{%s}}" fieldsJson
545+
546+
and encodeInputEnumSchemaJson (schema: InputEnumSchema) : string =
547+
let symbolsJson =
548+
schema.Symbols
549+
|> Seq.map (fun s -> sprintf "\"%s\"" (jsonEscapeString s))
550+
|> String.concat ","
551+
sprintf "{\"type\":\"enum\",\"symbols\":[%s]}" symbolsJson
552+
553+
and encodeInputArraySchemaJson (schema: InputArraySchema) : string =
554+
sprintf "{\"type\":\"array\",\"items\":%s}" (encodeCWLTypeJson schema.Items)
555+
556+
/// Convert a CWLType to a compact JSON string for use in JSON serialization
557+
let cwlTypeToJsonString (t: CWLType) : string =
558+
encodeCWLTypeJson t

0 commit comments

Comments
 (0)