From 1758b331c6b3303d6139b17d92db4833c12595ac Mon Sep 17 00:00:00 2001
From: Virgile Prevosto <virgile.prevosto@m4x.org>
Date: Fri, 7 Dec 2018 21:07:40 +0100
Subject: [PATCH] [sarif] proper handling of dictionaries + more regular
 structure

---
 src/plugins/markdown-report/sarif.ml | 1073 +++++++++++++++-----------
 1 file changed, 608 insertions(+), 465 deletions(-)

diff --git a/src/plugins/markdown-report/sarif.ml b/src/plugins/markdown-report/sarif.ml
index 18bb640cbb6..ce6f78f66ea 100644
--- a/src/plugins/markdown-report/sarif.ml
+++ b/src/plugins/markdown-report/sarif.ml
@@ -6,8 +6,8 @@
 
 module type Json_type = sig
   type t
-  val of_json: Yojson.Safe.json -> t Ppx_deriving_yojson_runtime.error_or
-  val to_json: t -> Yojson.Safe.json
+  val of_yojson: Yojson.Safe.json -> t Ppx_deriving_yojson_runtime.error_or
+  val to_yojson: t -> Yojson.Safe.json
 end
 
 module Json_dictionary(J: Json_type):
@@ -21,57 +21,68 @@ struct
     bind acc (fun acc -> (bindret (f x) (fun x -> (x :: acc))))
   let bind_list l f =
     bindret (List.fold_left (one_step (bind_pair f)) (Ok []) l) List.rev
-  let of_json = function
+  let of_yojson = function
     | `Assoc l ->
-      (match bind_list l J.of_json with
+      (match bind_list l J.of_yojson with
        | Error e -> Error ("dict." ^ e)
        | Ok _ as res -> res)
     | `Null -> Ok []
     | _ -> Error "dict"
-  let to_json l =
-    let json_l = List.map (fun (s, x) -> (s, J.to_json x)) l in
+  let to_yojson l =
+    let json_l = List.map (fun (s, x) -> (s, J.to_yojson x)) l in
     `Assoc json_l
 end
 
-type uri =
-  | Sarif_github [@name "https://github.com/oasis-tcs/sarif-spec/blob/master/Schemata/sarif-schema.json"]
-[@@deriving yojson]
+module Uri = struct
+  type t =
+    | Sarif_github [@name "https://github.com/oasis-tcs/sarif-spec/blob/master/Schemata/sarif-schema.json"]
+    [@@deriving yojson]
+end
 
-type version =
-  | V2_0_0 [@name "2.0.0"]
-[@@deriving yojson]
+module Version = struct
+  type t =
+    | V2_0_0 [@name "2.0.0"]
+  [@@deriving yojson]
+end
 
-(* not defined yet *)
-type message = {
- text: (string [@default ""]);
- messageId: (string [@default ""]);
- richText: (string [@default ""]);
- richMessageId: (string [@default ""]);
- arguments: (string list [@default []]);
-}[@@deriving yojson]
+module Message = struct
+  type t = {
+    text: (string [@default ""]);
+    messageId: (string [@default ""]);
+    richText: (string [@default ""]);
+    richMessageId: (string [@default ""]);
+    arguments: (string list [@default []]);
+  }[@@deriving yojson]
 
-let no_msg =
+let default =
   { text = "";
     messageId = "";
     richText = "";
     richMessageId = "";
     arguments = [];
   }
+end
+
+module FileLocation = struct
+  type t = {
+    uri: string;
+    uriBaseId: (string [@default ""])
+  }[@@deriving yojson]
 
-type fileLocation = {
-  uri: string;
-  uriBaseId: (string [@default ""])
- }[@@deriving yojson]
-let unknown_file = { uri = ""; uriBaseId = "" }
+  let default = { uri = ""; uriBaseId = "" }
+end
 
-type fileContent =
+module FileContent = struct
+type t =
   | Text of string [@name "text"]
   | Binary of string [@name "binary"]
 [@@deriving yojson]
 
-let no_file_content = Text ""
+let default = Text ""
+end
 
-type region = {
+module Region = struct
+type t = {
   startLine: (int [@default 0]);
   startColumn: (int [@default 0]);
   endLine: (int [@default 0]);
@@ -80,11 +91,11 @@ type region = {
   charLength: (int [@default 0]);
   byteOffset: (int [@default 0]);
   byteLength: (int [@default 0]);
-  snippet: (fileContent [@default no_file_content]);
-  message: (message [@default no_msg])
+  snippet: (FileContent.t [@default FileContent.default]);
+  message: (Message.t [@default Message.default])
 }[@@deriving yojson]
 
-let no_region = {
+let default = {
   startLine = 0;
   startColumn = 0;
   endLine = 0;
@@ -93,501 +104,633 @@ let no_region = {
   charLength = 0;
   byteOffset = 0;
   byteLength = 0;
-  snippet = no_file_content;
-  message = no_msg;
+  snippet = FileContent.default;
+  message = Message.default;
 }
+end
 
-type rectangle = {
-  top: (float [@default 0.]);
-  left: (float [@default 0.]);
-  bottom: (float [@default 0.]);
-  right: (float [@default 0.]);
-  message: (message [@default no_msg]);
+module Rectangle = struct
+  type t = {
+    top: (float [@default 0.]);
+    left: (float [@default 0.]);
+    bottom: (float [@default 0.]);
+    right: (float [@default 0.]);
+    message: (Message.t [@default Message.default]);
 }
 [@@deriving yojson]
+end
 
-type custom_properties = [ `Null | `Assoc of (string * Yojson.Safe.json) list ]
-[@@deriving yojson]
+module Custom_properties =
+  Json_dictionary(struct
+    type t = Yojson.Safe.json
+    let of_yojson x = Ok x
+    let to_yojson x = x
+  end)
 
-type properties = {
-  tags: string list;
-  additional_properties: (custom_properties [@default `Null])
-}
-[@@deriving yojson]
+module Properties = struct
+  type tags = string list [@@deriving yojson]
 
-let no_prop = { tags = []; additional_properties = `Null }
+  type t = {
+    tags: tags;
+    additional_properties: Custom_properties.t
+  }
 
-type physicalLocation = {
-  id: (string [@default ""]);
-  fileLocation: fileLocation;
-  region: (region [@default no_region]);
-  contextRegion: (region [@default no_region]);
-}[@@deriving yojson]
-let unknown_physicalLocation = {
-  id = "";
-  fileLocation = unknown_file;
-  region = no_region;
-  contextRegion = no_region;
-}
+  let default = { tags = []; additional_properties = [] }
 
-type location = {
-  physicalLocation: physicalLocation;
-  fullyQualifiedLogicalName: (string [@default ""]);
-  message: (message [@default no_msg]);
-  annotations: (region list [@default []]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
-let no_loc = {
-  physicalLocation = unknown_physicalLocation;
-  fullyQualifiedLogicalName = "";
-  message = no_msg;
-  annotations = [];
-  properties = no_prop
-}
+  let create additional_properties =
+    let tags = List.map fst additional_properties in
+    { tags; additional_properties }
 
-type stackFrame = {
-  location: (location [@default no_loc]);
-  stack_module: (string [@default ""])[@key "module"];
-  threadId: (int [@default 0]);
-  address: (int [@default 0]);
-  offset: (int [@default 0]);
-  parameters: (string list [@default []]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+  let of_yojson = function
+    | `Null -> Ok default
+    | `Assoc l ->
+      (match List.assoc_opt "tags" l with
+       | None -> Error "properties"
+       | Some json ->
+         (match tags_of_yojson json with
+          | Ok tags ->
+            let additional_properties = List.remove_assoc "tags" l in
+            Ok { tags; additional_properties }
+          | Error loc -> Error ("properties." ^ loc)))
+    | _ -> Error "properties"
+
+  let to_yojson { tags; additional_properties } =
+    match tags with
+    | [] -> `Null
+    | _ -> `Assoc (("tags", tags_to_yojson tags)::additional_properties)
+end
 
-type stack = {
-  message: (message [@default no_msg]);
-  frames: stackFrame list;
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module PhysicalLocation = struct
+  type t = {
+    id: (string [@default ""]);
+    fileLocation: FileLocation.t;
+    region: (Region.t [@default Region.default]);
+    contextRegion: (Region.t [@default Region.default]);
+  }[@@deriving yojson]
 
-let no_stack = {
-  message = no_msg;
-  frames = [];
-  properties = no_prop
-}
+  let default = {
+    id = "";
+    fileLocation = FileLocation.default;
+    region = Region.default;
+    contextRegion = Region.default;
+  }
+end
 
-(* TODO: this type definition is unclear in the schema. *)
-type additional_properties =
-  { additionalProperties: string }[@@deriving yojson]
+module Location = struct
+  type t = {
+    physicalLocation: PhysicalLocation.t;
+    fullyQualifiedLogicalName: (string [@default ""]);
+    message: (Message.t [@default Message.default]);
+    annotations: (Region.t list [@default []]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+  let default = {
+    physicalLocation = PhysicalLocation.default;
+    fullyQualifiedLogicalName = "";
+    message = Message.default;
+    annotations = [];
+    properties = Properties.default;
+  }
+end
 
-let no_additional_prop = { additionalProperties = "" }
+module StackFrame = struct
+  type t = {
+    location: (Location.t [@default Location.default]);
+    stack_module: (string [@default ""])[@key "module"];
+    threadId: (int [@default 0]);
+    address: (int [@default 0]);
+    offset: (int [@default 0]);
+    parameters: (string list [@default []]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type stl_importance =
-  | Important [@name "important"]
-  | Essential [@name "essential"]
-  | Unimportant [@name "unimportant"]
-[@@deriving yojson]
+module Stack = struct
+  type t = {
+    message: (Message.t [@default Message.default]);
+    frames: StackFrame.t list;
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
 
-type threadFlowLocation = {
-  step: int;
-  location: (location [@default no_loc]);
-  stack: (stack [@default no_stack]);
-  kind: (string [@default ""]);
-  tfl_module: (string [@default ""])[@key "module"];
-  state: (additional_properties [@default no_additional_prop]);
-  nestingLevel: (int [@default 0]);
-  executionOrder: (int [@default 0]);
-  timestamp: (string [@default ""]);
-  importance: (stl_importance [@default Unimportant]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+  let default = {
+    message = Message.default;
+    frames = [];
+    properties = Properties.default;
+  }
+end
 
-type threadFlow = {
-  id: (string [@default ""]);
-  message: (message [@default no_msg]);
-  locations: threadFlowLocation list;
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
 
-type attachment = {
- description: (message [@default no_msg ]);
- fileLocation: fileLocation;
- regions: (region list [@default []]);
- rectangles: (rectangle list [@default []])
-} [@@deriving yojson]
+module Additional_properties = struct
+  include Json_dictionary(struct type t = string[@@deriving yojson] end)
+
+  let default = []
+end
+
+module Stl_importance = struct
+  type t =
+    | Important [@name "important"]
+    | Essential [@name "essential"]
+    | Unimportant [@name "unimportant"]
+  [@@deriving yojson]
+end
 
-type codeFlow = {
-  description: (message [@default no_msg]);
-  threadFlows: threadFlow list;
-  properties: (properties [@default no_prop]);
-} [@@deriving yojson]
+module ThreadFlowLocation = struct
+  type t = {
+    step: int;
+    location: (Location.t [@default Location.default]);
+    stack: (Stack.t [@default Stack.default]);
+    kind: (string [@default ""]);
+    tfl_module: (string [@default ""])[@key "module"];
+    state: (Additional_properties.t [@default Additional_properties.default]);
+    nestingLevel: (int [@default 0]);
+    executionOrder: (int [@default 0]);
+    timestamp: (string [@default ""]);
+    importance: (Stl_importance.t [@default Stl_importance.Unimportant]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type sarif_exception = {
+module ThreadFlow = struct
+  type t = {
+    id: (string [@default ""]);
+    message: (Message.t [@default Message.default]);
+    locations: ThreadFlowLocation.t list;
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
+
+module Attachment = struct
+  type t = {
+    description: (Message.t [@default Message.default ]);
+    fileLocation: FileLocation.t;
+    regions: (Region.t list [@default []]);
+    rectangles: (Rectangle.t list [@default []])
+  } [@@deriving yojson]
+end
+
+module CodeFlow = struct
+  type t = {
+    description: (Message.t [@default Message.default]);
+    threadFlows: ThreadFlow.t list;
+    properties: (Properties.t [@default Properties.default]);
+  } [@@deriving yojson]
+end
+
+module Sarif_exception = struct
+type t = {
   kind: (string [@default ""]);
   message: (string [@default ""]);
-  stack: (stack [@default no_stack]);
-  innerExceptions: (sarif_exception list [@default []]);
+  stack: (Stack.t [@default Stack.default]);
+  innerExceptions: (t list [@default []]);
 }[@@deriving yojson]
-let no_exn = { kind = ""; message = ""; stack = no_stack; innerExceptions = [] }
 
-type notification_kind =
-  | Note [@name "note"]
-  | Warning [@name "warning"]
-  | Error [@name "error"]
-[@@deriving yojson]
+let default =
+  {
+    kind = "";
+    message = "";
+    stack = Stack.default;
+    innerExceptions = []
+  }
+end
 
-type notification = {
-  id: (string [@default ""]);
-  ruleId: (string [@default ""]);
-  physicalLocation: (physicalLocation [@default unknown_physicalLocation]);
-  message: message;
-  level: (notification_kind [@default Warning]);
-  threadId: (int [@default 0]);
-  time: (string [@default ""]);
-  exn: (sarif_exception [@default no_exn]) [@key "exception"];
-  properties: (properties [@default no_prop])
-}[@@deriving yojson]
+module Notification_kind = struct
+  type t =
+    | Note [@name "note"]
+    | Warning [@name "warning"]
+    | Error [@name "error"]
+  [@@deriving yojson]
+end
 
-type tool = {
-  name: string;
-  fullName: (string [@default ""]);
-  version: (string [@default ""]);
-  semanticVersion: (string [@default ""]);
-  fileVersion: (string [@default ""]);
-  downloadUri: (string [@default ""]);
-  sarifLoggerVersion: (string [@default ""]);
-  language: (string [@default "en-US"]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module Notification = struct
+  type t = {
+    id: (string [@default ""]);
+    ruleId: (string [@default ""]);
+    physicalLocation: (PhysicalLocation.t [@default PhysicalLocation.default]);
+    message: Message.t;
+    level: (Notification_kind.t [@default Notification_kind.Warning]);
+    threadId: (int [@default 0]);
+    time: (string [@default ""]);
+    exn: (Sarif_exception.t [@default Sarif_exception.default])
+        [@key "exception"];
+    properties: (Properties.t [@default Properties.default])
+  }[@@deriving yojson]
+end
 
-let no_tool = {
-  name = "";
-  fullName = "";
-  version = "";
-  semanticVersion = "";
-  fileVersion = "";
-  downloadUri = "";
-  sarifLoggerVersion = "";
-  language = "";
-  properties = no_prop;
-}
+module Tool = struct
+  type t = {
+    name: string;
+    fullName: (string [@default ""]);
+    version: (string [@default ""]);
+    semanticVersion: (string [@default ""]);
+    fileVersion: (string [@default ""]);
+    downloadUri: (string [@default ""]);
+    sarifLoggerVersion: (string [@default ""]);
+    language: (string [@default "en-US"]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
 
-type invocation = {
- commandLine: string;
- arguments: string list;
- responseFiles: (fileLocation list [@default []]);
- attachments: (attachment list [@default []]);
- startTime: (string [@default ""]);
- endTime: (string [@default ""]);
- exitCode: int;
- toolNotifications: (notification list [@default []]);
- configurationNotifications: (notification list [@default []]);
- exitCodeDescription: (string [@default ""]);
- exitSignalName: (string [@default ""]);
- exitSignalNumber: (int [@default 0]);
- processStartFailureMessage: (string [@default ""]);
- toolExecutionSuccessful: bool;
- machine: (string [@default ""]);
- account: (string [@default ""]);
- processId: (int [@default 0]);
- executableLocation: (fileLocation [@default unknown_file]);
- workingDirectory: (fileLocation [@default unknown_file]);
- environmentVariables: (additional_properties [@default no_additional_prop]);
- stdin: (fileLocation [@default unknown_file]);
- stdout: (fileLocation [@default unknown_file]);
- stderr: (fileLocation [@default unknown_file]);
- stdoutStderr: (fileLocation [@default unknown_file]);
- properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+  let default = {
+    name = "";
+    fullName = "";
+    version = "";
+    semanticVersion = "";
+    fileVersion = "";
+    downloadUri = "";
+    sarifLoggerVersion = "";
+    language = "";
+    properties = Properties.default;
+  }
+end
 
-let std_invocation = {
-  commandLine = "/bin/cat";
-  arguments = [];
-  responseFiles = [];
-  attachments = [];
-  startTime = "";
-  endTime = "";
-  exitCode = 0;
-  toolNotifications = [];
-  configurationNotifications = [];
-  exitCodeDescription = "";
-  exitSignalName = "";
-  exitSignalNumber = 0;
-  processStartFailureMessage = "";
-  toolExecutionSuccessful = true;
-  machine = "";
-  account = "";
-  processId = 0;
-  executableLocation = unknown_file;
-  workingDirectory = unknown_file;
-  environmentVariables = no_additional_prop;
-  stdin = unknown_file;
-  stdout = unknown_file;
-  stderr = unknown_file;
-  stdoutStderr = unknown_file;
-  properties = no_prop;
-}
+module Invocation = struct
+
+type t =  {
+    commandLine: string;
+    arguments: string list;
+    responseFiles: (FileLocation.t list [@default []]);
+    attachments: (Attachment.t list [@default []]);
+    startTime: (string [@default ""]);
+    endTime: (string [@default ""]);
+    exitCode: int;
+    toolNotifications: (Notification.t list [@default []]);
+    configurationNotifications: (Notification.t list [@default []]);
+    exitCodeDescription: (string [@default ""]);
+    exitSignalName: (string [@default ""]);
+    exitSignalNumber: (int [@default 0]);
+    processStartFailureMessage: (string [@default ""]);
+    toolExecutionSuccessful: bool;
+    machine: (string [@default ""]);
+    account: (string [@default ""]);
+    processId: (int [@default 0]);
+    executableLocation: (FileLocation.t [@default FileLocation.default]);
+    workingDirectory: (FileLocation.t [@default FileLocation.default]);
+    environmentVariables:
+      (Additional_properties.t [@default Additional_properties.default]);
+    stdin: (FileLocation.t [@default FileLocation.default]);
+    stdout: (FileLocation.t [@default FileLocation.default]);
+    stderr: (FileLocation.t [@default FileLocation.default]);
+    stdoutStderr: (FileLocation.t [@default FileLocation.default]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
 
-type conversion = {
-  tool: tool;
-  invocation: (invocation [@default std_invocation]);
-  analysisToolLogFiles: (fileLocation [@default unknown_file]);
-} [@@deriving yojson]
+  let default = {
+    commandLine = "/bin/cat";
+    arguments = [];
+    responseFiles = [];
+    attachments = [];
+    startTime = "";
+    endTime = "";
+    exitCode = 0;
+    toolNotifications = [];
+    configurationNotifications = [];
+    exitCodeDescription = "";
+    exitSignalName = "";
+    exitSignalNumber = 0;
+    processStartFailureMessage = "";
+    toolExecutionSuccessful = true;
+    machine = "";
+    account = "";
+    processId = 0;
+    executableLocation = FileLocation.default;
+    workingDirectory = FileLocation.default;
+    environmentVariables = Additional_properties.default;
+    stdin = FileLocation.default;
+    stdout = FileLocation.default;
+    stderr = FileLocation.default;
+    stdoutStderr = FileLocation.default;
+    properties = Properties.default;
+  }
+end
 
-let no_conversion = {
-  tool = no_tool;
-  invocation = std_invocation;
-  analysisToolLogFiles = unknown_file;
-}
+module Conversion = struct
+  type t = {
+    tool: Tool.t;
+    invocation: (Invocation.t [@default Invocation.default]);
+    analysisToolLogFiles: (FileLocation.t [@default FileLocation.default]);
+  } [@@deriving yojson]
+
+  let default = {
+    tool = Tool.default;
+    invocation = Invocation.default;
+    analysisToolLogFiles = FileLocation.default;
+  }
+end
 
-type edge = {
-  id: string;
-  label: (message [@default no_msg]);
-  sourceNodeId: string;
-  targetNodeId: string;
-  properties: (properties [@default no_prop])
-} [@@deriving yojson]
+module Edge = struct
+  type t  = {
+    id: string;
+    label: (Message.t [@default Message.default]);
+    sourceNodeId: string;
+    targetNodeId: string;
+    properties: (Properties.t [@default Properties.default])
+  } [@@deriving yojson]
+end
 
-type node =
-  { id: string;
+module Node = struct
+  type t = {
+    id: string;
     label: (string [@default ""]);
-    location: (location [@default no_loc]);
-    children: (node list [@default []]);
-    properties: (properties [@default no_prop]);
+    location: (Location.t [@default Location.default]);
+    children: (t list [@default []]);
+    properties: (Properties.t [@default Properties.default]);
   }[@@deriving yojson]
+end
 
-type edge_traversal = {
-  edgeId: string;
-  message: (message [@default no_msg]);
-  finalState: (additional_properties [@default no_additional_prop]);
-  stepOverEdgeCount: (int [@default 0]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module Edge_traversal = struct
+  type t = {
+    edgeId: string;
+    message: (Message.t [@default Message.default]);
+    finalState:
+      (Additional_properties.t [@default Additional_properties.default]);
+    stepOverEdgeCount: (int [@default 0]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type role =
-  | AnalysisTarget [@name "analysisTarget"]
-  | Attachment [@name "attachment"]
-  | ResponseFile [@name "responseFile"]
-  | ResultFile [@name "resultFile"]
-  | StandardStrem [@name "standardStream"]
-  | TraceFile [@name "traceFile"]
-  | UnmodifiedFile [@name "unmodifiedFile"]
-  | ModifiedFile [@name "modifiedFile"]
-  | AddedFile [@name "addedFile"]
-  | DeletedFile [@name "deletedFile"]
-  | RenamedFile [@name "renamedFile"]
-  | UncontrolledFile [@name "uncontrolledFile"]
-[@@deriving yojson]
+module Role = struct
+  type t =
+    | AnalysisTarget [@name "analysisTarget"]
+    | Attachment [@name "attachment"]
+    | ResponseFile [@name "responseFile"]
+    | ResultFile [@name "resultFile"]
+    | StandardStrem [@name "standardStream"]
+    | TraceFile [@name "traceFile"]
+    | UnmodifiedFile [@name "unmodifiedFile"]
+    | ModifiedFile [@name "modifiedFile"]
+    | AddedFile [@name "addedFile"]
+    | DeletedFile [@name "deletedFile"]
+    | RenamedFile [@name "renamedFile"]
+    | UncontrolledFile [@name "uncontrolledFile"]
+  [@@deriving yojson]
+end
 
-type hash = {
-  value: string;
-  algorithm: string
-} [@@deriving yojson]
-
-type graph = {
-  id : string;
-  description: (message [@default no_msg]);
-  nodes: node list;
-  edges: edge list;
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module Hash = struct
+  type t = {
+    value: string;
+    algorithm: string
+  } [@@deriving yojson]
+end
 
-type graph_dictionary =
-  [ `Null | `Assoc of (string * graph) list ][@@deriving yojson]
-let no_graph = `Null
+module Graph = struct
+  type t = {
+    id : string;
+    description: (Message.t [@default Message.default]);
+    nodes: Node.t list;
+    edges: Edge.t list;
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type graphTraversal = {
-  graphId: string;
-  description: (message [@default no_msg]);
-  initialState: (additional_properties [@default no_additional_prop]);
-  edgeTraversals: edge_traversal list;
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module Graph_dictionary = Json_dictionary(Graph)
 
-type replacement = {
-  deletedRegion: region;
-  insertedContent: (fileContent [@default no_file_content])
-}[@@deriving yojson]
+module GraphTraversal = struct
+  type t = {
+    graphId: string;
+    description: (Message.t [@default Message.default]);
+    initialState:
+      (Additional_properties.t [@default Additional_properties.default]);
+    edgeTraversals: Edge_traversal.t list;
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type file = {
-  fileLocation: (fileLocation [@default unknown_file]);
-  parentKey: (string [@default ""]);
-  offset: (int [@default 0]);
-  length: (int [@default 0]);
-  roles: (role list [@default []]);
-  mimeType: (string [@default ""]);
-  contents: (fileContent [@default no_file_content]);
-  encoding: (string [@default ""]);
-  hashes: (hash list [@default []]);
-  lastModifiedTime: (string [@default ""]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module Replacement = struct
+  type t = {
+    deletedRegion: Region.t;
+    insertedContent: (FileContent.t [@default FileContent.default])
+  }[@@deriving yojson]
+end
 
-type fileChange = {
-  fileLocation: fileLocation;
-  replacements: replacement list
-}[@@deriving yojson]
+module File = struct
+  type t = {
+    fileLocation: (FileLocation.t [@default FileLocation.default]);
+    parentKey: (string [@default ""]);
+    offset: (int [@default 0]);
+    length: (int [@default 0]);
+    roles: (Role.t list [@default []]);
+    mimeType: (string [@default ""]);
+    contents: (FileContent.t [@default FileContent.default]);
+    encoding: (string [@default ""]);
+    hashes: (Hash.t list [@default []]);
+    lastModifiedTime: (string [@default ""]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type fix = {
-  description: (message [@defaut no_msg]);
-  fileChanges: fileChange list;
-}[@@deriving yojson]
+module FileChange = struct
+  type t = {
+    fileLocation: FileLocation.t;
+    replacements: Replacement.t list
+  }[@@deriving yojson]
+end
 
-type externalFiles = {
-  conversion: (fileLocation [@default unknown_file]);
-  files: (fileLocation [@default unknown_file]);
-  graphs: (fileLocation [@default unknown_file]);
-  invocations: (fileLocation list [@default []]);
-  logicalLocations: (fileLocation [@default unknown_file]);
-  resources: (fileLocation [@default unknown_file]);
-  results: (fileLocation [@default unknown_file]);
-}[@@deriving yojson]
+module Fix = struct
+  type t = {
+    description: (Message.t [@defaut Message.default]);
+    fileChanges: FileChange.t list;
+  }[@@deriving yojson]
+end
 
-type logicalLocation = {
-  name: string;
-  fullyQualifiedName: string;
-  decoratedName: string;
-  parentKey: string;
-  kind: string;
-}[@@deriving yojson]
+module ExternalFiles = struct
+  type t = {
+    conversion: (FileLocation.t [@default FileLocation.default]);
+    files: (FileLocation.t [@default FileLocation.default]);
+    graphs: (FileLocation.t [@default FileLocation.default]);
+    invocations: (FileLocation.t list [@default []]);
+    logicalLocations: (FileLocation.t [@default FileLocation.default]);
+    resources: (FileLocation.t [@default FileLocation.default]);
+    results: (FileLocation.t [@default FileLocation.default]);
+  }[@@deriving yojson]
+end
 
-type ruleConfigLevel =
-  | Note [@name "note"]
-  | Warning [@name "warning"]
-  | Error [@name "error"]
-  | Open [@name "open"]
-[@@deriving yojson]
+module LogicalLocation = struct
+  type t = {
+    name: string;
+    fullyQualifiedName: string;
+    decoratedName: string;
+    parentKey: string;
+    kind: string;
+  }[@@deriving yojson]
+end
 
-type ruleConfiguration = {
-  enabled: (bool [@default false]);
-  defaultLevel: (ruleConfigLevel [@default Open]);
-  parameters: (properties [@default no_prop])
-}[@@deriving yojson]
+module RuleConfigLevel = struct
+  type t =
+    | Note [@name "note"]
+    | Warning [@name "warning"]
+    | Error [@name "error"]
+    | Open [@name "open"]
+  [@@deriving yojson]
+end
 
-let std_rule_config = {
-  enabled = false;
-  defaultLevel = Open;
-  parameters = no_prop;
-}
+module RuleConfiguration = struct
+  type t = {
+    enabled: (bool [@default false]);
+    defaultLevel: (RuleConfigLevel.t [@default RuleConfigLevel.Open]);
+    parameters: (Properties.t [@default Properties.default])
+  }[@@deriving yojson]
 
-type rule = {
-  id: (string [@default ""]);
-  name: (string [@default ""]);
-  shortDescription: (message [@default no_msg]);
-  fullDescription: (message [@default no_msg]);
-  messageStrings: (additional_properties [@default no_additional_prop]);
-  richMessageStrings: (additional_properties [@default no_additional_prop]);
-  configuration: (ruleConfiguration [@default std_rule_config]);
-  helpUri: (string [@default ""]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+  let default = {
+    enabled = false;
+    defaultLevel = RuleConfigLevel.Open;
+    parameters = Properties.default;
+  }
+end
 
-let no_rule = {
-  id = "";
-  name = "";
-  shortDescription = no_msg;
-  fullDescription = no_msg;
-  messageStrings = no_additional_prop;
-  richMessageStrings = no_additional_prop;
-  configuration = std_rule_config;
-  helpUri = "";
-  properties = no_prop;
-}
+module Rule = struct
+  type t = {
+    id: (string [@default ""]);
+    name: (string [@default ""]);
+    shortDescription: (Message.t [@default Message.default]);
+    fullDescription: (Message.t [@default Message.default]);
+    messageStrings:
+      (Additional_properties.t [@default Additional_properties.default]);
+    richMessageStrings:
+      (Additional_properties.t [@default Additional_properties.default]);
+    configuration: (RuleConfiguration.t [@default RuleConfiguration.default]);
+    helpUri: (string [@default ""]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
 
-type resources = {
-  messageStrings: (additional_properties [@default no_additional_prop]);
-  rules: (rule [@default no_rule]);
-}[@@deriving yojson]
+  let default = {
+    id = "";
+    name = "";
+    shortDescription = Message.default;
+    fullDescription = Message.default;
+    messageStrings = Additional_properties.default;
+    richMessageStrings = Additional_properties.default;
+    configuration = RuleConfiguration.default;
+    helpUri = "";
+    properties = Properties.default;
+  }
+end
 
-let no_resources = { messageStrings = no_additional_prop; rules = no_rule }
+module Rule_dictionary = Json_dictionary(Rule)
 
-type result_level =
-  | NotApplicable [@name "notApplicable"]
-  | Pass [@name "pass"]
-  | Note [@name "note"]
-  | Warning [@name "warning"]
-  | Error [@name "error"]
-[@@deriving yojson]
+module Resources = struct
+  type t = {
+    messageStrings:
+      (Additional_properties.t [@default Additional_properties.default]);
+    rules: (Rule_dictionary.t [@default []]);
+  }[@@deriving yojson]
 
-type result_suppressionState =
-  | SuppressedInSource [@name "suppressedInSource"]
-  | SuppressedExternally [@name "suppressedExternally"]
-[@@deriving yojson]
+  let default = {
+    messageStrings = Additional_properties.default;
+    rules = [] }
+end
 
-type result_baselineState =
-  | New [@name "new"]
-  | Existing [@name "existing"]
-  | Absent [@name "absent"]
-[@@deriving yojson]
+module Result_level = struct
+  type t = 
+    | NotApplicable [@name "notApplicable"]
+    | Pass [@name "pass"]
+    | Note [@name "note"]
+    | Warning [@name "warning"]
+    | Error [@name "error"]
+  [@@deriving yojson]
+end
 
-type result = {
-  ruleId: (string [@default ""]);
-  level: (result_level [@default NotApplicable]);
-  message: (message [@default no_msg]);
-  analysisTarget: (fileLocation [@default unknown_file]);
-  locations: (location list [@default []]);
-  instanceGuid: (string [@default ""]);
-  correlationGuid: (string [@default ""]);
-  occurenceCount: (int [@default 1]);
-  partialFingerprints: (additional_properties [@default no_additional_prop]);
-  fingerprints: (additional_properties [@default no_additional_prop]);
-  stacks: (stack list [@default []]);
-  codeFlows: (codeFlow list [@default []]);
-  graphs: (graph_dictionary [@default no_graph]);
-  graphTraversals: (graphTraversal list [@default []]);
-  relatedLocations: (location list [@default []]);
-  suppressionStates: (result_suppressionState list [@default []]);
-  baselineState: (result_baselineState [@default Absent]);
-  attachments: (attachment list [@default []]);
-  workItemsUris: (string list [@default []]);
-  conversionProvenance: (physicalLocation list [@default[]]);
-  fixes: (fix list [@default []]);
-  properties: (properties [@default no_prop])
-}[@@deriving yojson]
+module Result_suppressionState = struct
+  type t =
+    | SuppressedInSource [@name "suppressedInSource"]
+    | SuppressedExternally [@name "suppressedExternally"]
+  [@@deriving yojson]
+end
 
-type versionControlDetails = {
-  uri: string;
-  revisionId: (string [@default ""]);
-  branch: (string [@default ""]);
-  tag: (string [@default ""]);
-  timestamp: (string [@default ""]);
-  properties: (properties [@default no_prop]);
-}[@@deriving yojson]
+module Result_baselineState = struct
+  type t =
+    | New [@name "new"]
+    | Existing [@name "existing"]
+    | Absent [@name "absent"]
+  [@@deriving yojson]
+end
 
-(** TODO: `Assoc should take as argument a json rep of a file *)
-type file_dictionary =
-  [ `Null | `Assoc of string * Yojson.Safe.json][@@deriving yojson]
-let no_file = `Null
+(* we can't use Result here, as this would conflict with
+   Ppx_deriving_yojson_runtime.Result that is opened by the
+   code generated by Ppx_deriving_yojson. *)
+module Sarif_result: Json_type = struct
+  type t = {
+    ruleId: (string [@default ""]);
+    level: (Result_level.t [@default Result_level.NotApplicable]);
+    message: (Message.t [@default Message.default]);
+    analysisTarget: (FileLocation.t [@default FileLocation.default]);
+    locations: (Location.t list [@default []]);
+    instanceGuid: (string [@default ""]);
+    correlationGuid: (string [@default ""]);
+    occurenceCount: (int [@default 1]);
+    partialFingerprints:
+      (Additional_properties.t [@default Additional_properties.default]);
+    fingerprints:
+      (Additional_properties.t [@default Additional_properties.default]);
+    stacks: (Stack.t list [@default []]);
+    codeFlows: (CodeFlow.t list [@default []]);
+    graphs: (Graph_dictionary.t [@default []]);
+    graphTraversals: (GraphTraversal.t list [@default []]);
+    relatedLocations: (Location.t list [@default []]);
+    suppressionStates: (Result_suppressionState.t list [@default []]);
+    baselineState:
+      (Result_baselineState.t [@default Result_baselineState.Absent]);
+    attachments: (Attachment.t list [@default []]);
+    workItemsUris: (string list [@default []]);
+    conversionProvenance: (PhysicalLocation.t list [@default[]]);
+    fixes: (Fix.t list [@default []]);
+    properties: (Properties.t [@default Properties.default])
+  }[@@deriving yojson]
+end
 
-(** TODO: values are logicalLocation *)
-type logical_loc_dict =
-  [ `Null | `Assoc of string * logicalLocation][@@deriving yojson]
-let no_location = `Null
+module VersionControlDetails = struct
+  type t = {
+    uri: string;
+    revisionId: (string [@default ""]);
+    branch: (string [@default ""]);
+    tag: (string [@default ""]);
+    timestamp: (string [@default ""]);
+    properties: (Properties.t [@default Properties.default]);
+  }[@@deriving yojson]
+end
 
-type columnKind =
-  | Utf16CodeUnits [@name "utf16CodeUnits"]
-  | UnicodeCodePoints [@name "unicodeCodePoints"]
-[@@deriving yojson]
+module File_dictionary = Json_dictionary(File)
+
+module LogicalLocation_dictionary = Json_dictionary(LogicalLocation)
 
-type run = {
-  tool: tool;
-  invocations: (invocation list [@default []]);
-  conversion: (conversion [@default no_conversion]);
-  versionControlProvenance: (versionControlDetails list [@default []]);
-  originalUriBaseIds: (additional_properties [@default no_additional_prop]);
-  files: (file_dictionary [@default no_file]);
-  logicalLocations: (logical_loc_dict [@default no_location]);
-  graphs: (graph_dictionary [@default no_graph]);
-  results: (result list [@default []]);
-  resources: (resources [@default no_resources]);
-  instanceGuid: (string [@default ""]);
-  correlationGuid: (string [@default ""]);
-  logicalId: (string [@default ""]);
-  description: (message [@default no_msg]);
-  automationLogicalId: (string [@default ""]);
-  baselineInstanceGuid: (string [@default ""]);
-  architecture: (string [@default ""]);
-  richMessageMimeType: (string [@default "text/markdown;variant=GFM" ]);
-  redactionToken: (string [@default ""]);
-  defaultFileEncoding: (string [@default "utf-8"]);
-  columnKind: (columnKind [@default UnicodeCodePoints]);
-  properties: (properties [@default no_prop]);
+module ColumnKind = struct
+  type t =
+    | Utf16CodeUnits [@name "utf16CodeUnits"]
+    | UnicodeCodePoints [@name "unicodeCodePoints"]
+  [@@deriving yojson]
+end
+
+module Run = struct
+  type t = {
+    tool: Tool.t;
+    invocations: (Invocation.t list [@default []]);
+    conversion: (Conversion.t [@default Conversion.default]);
+    versionControlProvenance: (VersionControlDetails.t list [@default []]);
+    originalUriBaseIds:
+      (Additional_properties.t [@default Additional_properties.default]);
+    files: (File_dictionary.t [@default []]);
+    logicalLocations: (LogicalLocation_dictionary.t [@default []]);
+    graphs: (Graph_dictionary.t [@default []]);
+    results: (Sarif_result.t list [@default []]);
+    resources: (Resources.t [@default Resources.default]);
+    instanceGuid: (string [@default ""]);
+    correlationGuid: (string [@default ""]);
+    logicalId: (string [@default ""]);
+    description: (Message.t [@default Message.default]);
+    automationLogicalId: (string [@default ""]);
+    baselineInstanceGuid: (string [@default ""]);
+    architecture: (string [@default ""]);
+    richMessageMimeType: (string [@default "text/markdown;variant=GFM" ]);
+    redactionToken: (string [@default ""]);
+    defaultFileEncoding: (string [@default "utf-8"]);
+    columnKind: (ColumnKind.t [@default UnicodeCodePoints]);
+    properties: (Properties.t [@default Properties.default]);
 }
 [@@deriving yojson]
+end
 
-type schema = {
-  schema: (uri [@default Sarif_github]) [@key "$schema"];
-  version: version;
-  runs: run list
-} [@@deriving yojson]
+module Schema = struct
+  type t = {
+    schema: (Uri.t [@default Uri.Sarif_github]) [@key "$schema"];
+    version: Version.t;
+    runs: Run.t list
+  } [@@deriving yojson]
+end
-- 
GitLab