packages feed

hydra-0.15.0: src/main/haskell/Hydra/Sources/Pg/Coder.hs

module Hydra.Sources.Pg.Coder where

-- Standard imports for term-level sources outside of the kernel
import Hydra.Kernel hiding (
  check, checkRecordName, constructEdgeCoder, constructVertexCoder,
  edgeCoder, edgeIdAdapter, elementCoder, elementTreeEdge, elementTreeVertex,
  elementTypeTreeEdge, elementTypeTreeVertex, encodeProperties, encodeProperty,
  extractString, findAdjacenEdgeAdapters, findIdProjectionSpec,
  findIncidentVertexAdapter, findLabelString, findProjectionSpec,
  findPropertySpecs, findSingleFieldWithAnnotationKey, hasVertexAdapters,
  projectionAdapter, propertyAdapter, propertyTypes, selectEdgeId,
  selectVertexId, traverseToSingleTerm, vertexCoder, vertexIdAdapter)
import Hydra.Sources.Libraries
import           Hydra.Dsl.Meta.Lib.Strings                as Strings
import           Hydra.Dsl.Meta.Phantoms                   as Phantoms
import qualified Hydra.Dsl.Annotations                     as Annotations
import qualified Hydra.Dsl.Bootstrap                       as Bootstrap
import qualified Hydra.Dsl.LiteralTypes                    as LiteralTypes
import qualified Hydra.Dsl.Literals                        as Literals
import qualified Hydra.Dsl.Paths                      as Paths
import qualified Hydra.Dsl.Ast                        as Ast
import qualified Hydra.Dsl.Meta.Base                       as MetaBase
import qualified Hydra.Dsl.Coders                     as Coders
import qualified Hydra.Dsl.Util                    as Util
import qualified Hydra.Dsl.Meta.Context                    as Ctx
import qualified Hydra.Dsl.Errors                      as Error
import qualified Hydra.Dsl.Meta.Core                       as Core
import qualified Hydra.Dsl.Meta.Graph                      as Graph
import qualified Hydra.Dsl.Json.Model                       as Json
import qualified Hydra.Dsl.Meta.Lib.Chars                  as Chars
import qualified Hydra.Dsl.Meta.Lib.Eithers                as Eithers
import qualified Hydra.Dsl.Meta.Lib.Equality               as Equality
import qualified Hydra.Dsl.Meta.Lib.Lists                  as Lists
import qualified Hydra.Dsl.Meta.Lib.Literals               as Literals
import qualified Hydra.Dsl.Meta.Lib.Logic                  as Logic
import qualified Hydra.Dsl.Meta.Lib.Maps                   as Maps
import qualified Hydra.Dsl.Meta.Lib.Math                   as Math
import qualified Hydra.Dsl.Meta.Lib.Maybes                 as Maybes
import qualified Hydra.Dsl.Meta.Lib.Pairs                  as Pairs
import qualified Hydra.Dsl.Meta.Lib.Sets                   as Sets
import qualified Hydra.Dsl.Packaging                     as Packaging
import qualified Hydra.Dsl.Meta.Terms                      as MetaTerms
import qualified Hydra.Dsl.Meta.Testing                    as Testing
import qualified Hydra.Dsl.Topology                   as Topology
import qualified Hydra.Dsl.Meta.Types                      as MetaTypes
import qualified Hydra.Dsl.Typing                     as Typing
import qualified Hydra.Dsl.Util                       as Util
import qualified Hydra.Encode.Core                         as EncodeCore
import qualified Hydra.Dsl.Meta.Variants                   as Variants
import qualified Hydra.Dsl.Prims                           as Prims
import qualified Hydra.Dsl.Meta.Tabular                         as Tabular
import qualified Hydra.Dsl.Terms                           as Terms
import qualified Hydra.Dsl.Tests                           as Tests
import qualified Hydra.Dsl.Types                           as Types
import qualified Hydra.Sources.Decode.Core                 as DecodeCore
import qualified Hydra.Sources.Encode.Core                 as EncodeCore
import qualified Hydra.Sources.Kernel.Terms.Adapt           as Adapt
import qualified Hydra.Sources.Kernel.Terms.All            as KernelTerms
import qualified Hydra.Sources.Kernel.Terms.Annotations    as Annotations
import qualified Hydra.Sources.Kernel.Terms.Arity          as Arity
import qualified Hydra.Sources.Kernel.Terms.Checking       as Checking
import qualified Hydra.Sources.Kernel.Terms.Constants      as Constants
import qualified Hydra.Sources.Kernel.Terms.Extract.Core   as ExtractCore
import qualified Hydra.Sources.Kernel.Terms.Extract.Util   as ExtractUtil
import qualified Hydra.Sources.Kernel.Terms.Formatting     as Formatting
import qualified Hydra.Sources.Kernel.Terms.Inference      as Inference
import qualified Hydra.Sources.Kernel.Terms.Languages      as Languages
import qualified Hydra.Sources.Kernel.Terms.Lexical        as Lexical
import qualified Hydra.Sources.Kernel.Terms.Literals       as Literals
import qualified Hydra.Sources.Kernel.Terms.Names          as Names
import qualified Hydra.Sources.Kernel.Terms.Reduction      as Reduction
import qualified Hydra.Sources.Kernel.Terms.Reflect        as Reflect
import qualified Hydra.Sources.Kernel.Terms.Strip          as Strip
import qualified Hydra.Sources.Kernel.Terms.Resolution    as Resolution
import qualified Hydra.Sources.Kernel.Terms.Serialization  as Serialization
import qualified Hydra.Sources.Kernel.Terms.Show.Paths as ShowPaths
import qualified Hydra.Sources.Kernel.Terms.Show.Core      as ShowCore
import qualified Hydra.Sources.Kernel.Terms.Show.Graph     as ShowGraph
import qualified Hydra.Sources.Kernel.Terms.Show.Variants  as ShowVariants
import qualified Hydra.Sources.Kernel.Terms.Show.Typing    as ShowTyping
import qualified Hydra.Sources.Kernel.Terms.Sorting        as Sorting
import qualified Hydra.Sources.Kernel.Terms.Substitution   as Substitution
import qualified Hydra.Sources.Kernel.Terms.Templates      as Templates
import qualified Hydra.Sources.Kernel.Terms.Unification    as Unification
import qualified Hydra.Sources.Kernel.Types.All            as KernelTypes
import           Prelude hiding ((++))
import qualified Data.Int                                  as I
import qualified Data.List                                 as L
import qualified Data.Map                                  as M
import qualified Data.Set                                  as S
import qualified Data.Maybe                                as Y

-- Additional imports
import qualified Hydra.Pg.Model as PG
import qualified Hydra.Pg.Mapping as PGM
import qualified Hydra.Sources.Pg.Model as PgModel
import qualified Hydra.Sources.Pg.Mapping as PgMapping
import qualified Hydra.Sources.Pg.TermsToElements as TermsToElements


define :: String -> TTerm a -> TTermDefinition a
define = definitionInModule module_

ns :: Namespace
ns = Namespace "hydra.pg.coder"

module_ :: Module
module_ = Module {
            moduleNamespace = ns,
            moduleDefinitions = definitions,
            moduleTermDependencies = [Annotations.ns, ExtractCore.ns, Resolution.ns, TermsToElements.ns],
            moduleTypeDependencies = (PgModel.ns:PgMapping.ns:KernelTypes.kernelTypesNamespaces),
            moduleDescription = Just "Property graph element coders for mapping Hydra terms to property graph elements"}
  where
    definitions = [
      toDefinition check,
      toDefinition checkRecordName,
      toDefinition constructEdgeCoder,
      toDefinition constructVertexCoder,
      toDefinition edgeCoder,
      toDefinition edgeIdAdapter,
      toDefinition elementCoder,
      toDefinition elementTreeEdge,
      toDefinition elementTreeVertex,
      toDefinition elementTypeTreeEdge,
      toDefinition elementTypeTreeVertex,
      toDefinition encodeProperties,
      toDefinition encodeProperty,
      toDefinition extractString,
      toDefinition findAdjacenEdgeAdapters,
      toDefinition findIdProjectionSpec,
      toDefinition findIncidentVertexAdapter,
      toDefinition findLabelString,
      toDefinition findProjectionSpec,
      toDefinition findPropertySpecs,
      toDefinition findSingleFieldWithAnnotationKey,
      toDefinition hasVertexAdapters,
      toDefinition projectionAdapter,
      toDefinition propertyAdapter,
      toDefinition propertyTypes,
      toDefinition selectEdgeId,
      toDefinition selectVertexId,
      toDefinition traverseToSingleTerm,
      toDefinition vertexCoder,
      toDefinition vertexIdAdapter]

-- Helper for error results
err :: TTerm Context -> TTerm String -> TTerm (Either Error a)
err cx msg = left $ Error.errorOther $ Error.otherError msg

unexpectedE :: TTerm Context -> TTerm String -> TTerm String -> TTerm (Either Error a)
unexpectedE cx expected found = err cx (string "Expected " ++ expected ++ string ", found: " ++ found)

-- | Check a condition, returning an error if false
check :: TTermDefinition (Context -> Bool -> Either Error () -> Either Error ())
check = define "check" $
  doc "Check a condition, returning an error if false" $
  "_cx" ~> "b" ~> "e" ~>
    Logic.ifElse (var "b") (right unit) (var "e")

-- | Check that a record name matches the expected name.
--   Skips the check when the expected name is a placeholder (used when the type has no intrinsic name).
checkRecordName :: TTermDefinition (Context -> Name -> Name -> Either Error ())
checkRecordName = define "checkRecordName" $
  doc "Check that a record name matches the expected name" $
  "cx" ~> "expected" ~> "actual" ~>
    check @@ var "cx"
      @@ (Logic.or
        (Equality.equal (Core.unName $ var "expected") (string "placeholder"))
        (Equality.equal (Core.unName $ var "actual") (Core.unName $ var "expected")))
      @@ (err (var "cx") (string "Expected record of type " ++ (Core.unName $ var "expected") ++ string ", found record of type " ++ (Core.unName $ var "actual")))

-- | Construct an edge coder from components
constructEdgeCoder :: TTermDefinition (Context -> Graph -> PG.VertexLabel -> PGM.Schema Graph t v -> Type -> t -> t -> PG.Direction -> Name -> [FieldType]
  -> [Adapter FieldType (PG.PropertyType t) Field (PG.Property v)]
  -> Y.Maybe (FieldType, PGM.ValueSpec, Y.Maybe String)
  -> Y.Maybe (FieldType, PGM.ValueSpec, Y.Maybe String)
  -> Either Error (Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v)))
constructEdgeCoder = define "constructEdgeCoder" $
  doc "Construct an edge coder from components" $
  "cx" ~> "g" ~> "parentLabel" ~> "schema" ~> "source" ~> "vidType" ~> "eidType" ~> "dir" ~> "name" ~> "fields" ~>
  "propAdapters" ~> "mOutSpec" ~> "mInSpec" ~>
    Eithers.bind (findLabelString @@ var "cx" @@ var "g" @@ var "source" @@ var "name" @@ (Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_edgeLabel @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema")))
      ("labelStr" ~> lets [
        "label">: wrap PG._EdgeLabel $ var "labelStr",
        "vertexIdsSchema">: project PGM._Schema PGM._Schema_vertexIds @@ var "schema"] $
        Eithers.bind (edgeIdAdapter @@ var "cx" @@ var "g" @@ var "schema" @@ var "eidType" @@ var "name"
          @@ (Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_edgeId @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"))
          @@ var "fields")
          ("idAdapter" ~>
            -- Compute out/in id adapters from projection specs
            Eithers.bind (Maybes.maybe (right nothing)
              ("s" ~> Eithers.map (lambda "x" $ just (var "x")) (projectionAdapter @@ var "cx" @@ var "g" @@ var "vidType" @@ var "vertexIdsSchema" @@ var "s" @@ string "out"))
              (var "mOutSpec"))
              ("outIdAdapter" ~> Eithers.bind (Maybes.maybe (right nothing)
                ("s" ~> Eithers.map (lambda "x" $ just (var "x")) (projectionAdapter @@ var "cx" @@ var "g" @@ var "vidType" @@ var "vertexIdsSchema" @@ var "s" @@ string "in"))
                (var "mInSpec"))
                ("inIdAdapter" ~>
                  -- Compute out/in vertex adapters from projection specs
                  Eithers.bind (Maybes.maybe (right nothing)
                    ("s" ~> Eithers.map (lambda "x" $ just (var "x")) (findIncidentVertexAdapter @@ var "cx" @@ var "g" @@ var "schema" @@ var "vidType" @@ var "eidType" @@ var "s"))
                    (var "mOutSpec"))
                    ("outVertexAdapter" ~> Eithers.bind (Maybes.maybe (right nothing)
                      ("s" ~> Eithers.map (lambda "x" $ just (var "x")) (findIncidentVertexAdapter @@ var "cx" @@ var "g" @@ var "schema" @@ var "vidType" @@ var "eidType" @@ var "s"))
                      (var "mInSpec"))
                      ("inVertexAdapter" ~> lets [
                        "vertexAdapters">: Maybes.cat (list [var "outVertexAdapter", var "inVertexAdapter"])] $
                        -- Compute out/in vertex labels from spec aliases or fall back to parentLabel
                        Eithers.bind (Maybes.maybe (right $ var "parentLabel")
                          ("spec" ~> Maybes.maybe
                            (left $ Error.errorOther $ Error.otherError $ string "no out-vertex label")
                            (lambda "a" $ right $ wrap PG._VertexLabel (var "a"))
                            (Pairs.second $ Pairs.second $ var "spec"))
                          (var "mOutSpec"))
                          ("outLabel" ~> Eithers.bind (Maybes.maybe (right $ var "parentLabel")
                            ("spec" ~> Maybes.maybe
                              (left $ Error.errorOther $ Error.otherError $ string "no in-vertex label")
                              (lambda "a" $ right $ wrap PG._VertexLabel (var "a"))
                              (Pairs.second $ Pairs.second $ var "spec"))
                            (var "mInSpec"))
                            ("inLabel" ~>
                              right (edgeCoder @@ var "g" @@ var "dir" @@ var "schema" @@ var "source" @@ var "eidType" @@ var "name" @@ var "label"
                                @@ var "outLabel" @@ var "inLabel" @@ var "idAdapter" @@ var "outIdAdapter" @@ var "inIdAdapter" @@ var "propAdapters" @@ var "vertexAdapters")))))))))

-- | Construct a vertex coder from components
constructVertexCoder :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> Type -> t -> t -> Name -> [FieldType]
  -> [Adapter FieldType (PG.PropertyType t) Field (PG.Property v)]
  -> Either Error (Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v)))
constructVertexCoder = define "constructVertexCoder" $
  doc "Construct a vertex coder from components" $
  "cx" ~> "g" ~> "schema" ~> "source" ~> "vidType" ~> "eidType" ~> "name" ~> "fields" ~> "propAdapters" ~>
    Eithers.bind (findLabelString @@ var "cx" @@ var "g" @@ var "source" @@ var "name" @@ (Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_vertexLabel @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema")))
      ("labelStr" ~> lets [
        "label">: wrap PG._VertexLabel $ var "labelStr"] $
        Eithers.bind (vertexIdAdapter @@ var "cx" @@ var "g" @@ var "schema" @@ var "vidType" @@ var "name"
          @@ (Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_vertexId @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"))
          @@ var "fields")
          ("idAdapter" ~>
            Eithers.bind (findAdjacenEdgeAdapters @@ var "cx" @@ var "g" @@ var "schema" @@ var "vidType" @@ var "eidType" @@ var "label" @@ (inject PG._Direction PG._Direction_out unit) @@ var "fields")
              ("outEdgeAdapters" ~> Eithers.bind (findAdjacenEdgeAdapters @@ var "cx" @@ var "g" @@ var "schema" @@ var "vidType" @@ var "eidType" @@ var "label" @@ (inject PG._Direction PG._Direction_in unit) @@ var "fields")
                ("inEdgeAdapters" ~>
                  right (vertexCoder @@ var "g" @@ var "schema" @@ var "source" @@ var "vidType" @@ var "name" @@ var "label"
                    @@ var "idAdapter" @@ var "propAdapters" @@ (Lists.concat2 (var "outEdgeAdapters") (var "inEdgeAdapters")))))))

-- | Create an edge coder given all components
edgeCoder :: TTermDefinition (Graph -> PG.Direction -> PGM.Schema Graph t v -> Type -> t -> Name
  -> PG.EdgeLabel -> PG.VertexLabel -> PG.VertexLabel
  -> Y.Maybe (Name, Adapter Type t Term v) -> Y.Maybe (Name, Adapter Type t Term v) -> Y.Maybe (Name, Adapter Type t Term v)
  -> [Adapter FieldType (PG.PropertyType t) Field (PG.Property v)]
  -> [(Name, Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v))]
  -> Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v))
edgeCoder = define "edgeCoder" $
  doc "Create an edge coder given all components" $
  "g" ~> "dir" ~> "schema" ~> "source" ~> "eidType" ~> "tname" ~> "label" ~> "outLabel" ~> "inLabel" ~>
  "mIdAdapter" ~> "outAdapter" ~> "inAdapter" ~> "propAdapters" ~> "vertexAdapters" ~> lets [
    "et">: record PG._EdgeType [
      PG._EdgeType_label>>: var "label",
      PG._EdgeType_id>>: var "eidType",
      PG._EdgeType_out>>: var "outLabel",
      PG._EdgeType_in>>: var "inLabel",
      PG._EdgeType_properties>>: propertyTypes @@ var "propAdapters"]] $
    Coders.adapter true (var "source")
      (elementTypeTreeEdge @@ var "et" @@ TTerm (Terms.list []))
      (Coders.coder
        ("cx" ~> "term" ~>
          lets ["deannot">: Strip.deannotateTerm @@ var "term",
                "unwrapped">: cases _Term (var "deannot") (Just $ var "deannot") [
                  _Term_maybe>>: "mt" ~> Maybes.fromMaybe (var "deannot") (var "mt")],
                "rec">: cases _Term (var "unwrapped") Nothing [
                  _Term_record>>: lambda "r" $ var "r"]] $
          Eithers.bind (checkRecordName @@ var "cx" @@ var "tname" @@ (Core.recordTypeName $ var "rec"))
            ("_chk" ~> lets [
              "fieldsm">: Resolution.fieldMap @@ (Core.recordFields $ var "rec")] $
              Eithers.bind (Maybes.maybe
                (right $ project PGM._Schema PGM._Schema_defaultEdgeId @@ var "schema")
                (selectEdgeId @@ var "cx" @@ var "fieldsm")
                (var "mIdAdapter"))
                ("edgeId" ~> Eithers.bind (encodeProperties @@ var "cx" @@ var "fieldsm" @@ var "propAdapters")
                  ("props" ~> lets [
                    "getVertexId">: "dirCheck" ~> "adapter" ~>
                      Maybes.maybe
                        (right $ project PGM._Schema PGM._Schema_defaultVertexId @@ var "schema")
                        (selectVertexId @@ var "cx" @@ var "fieldsm")
                        (Logic.ifElse (Equality.equal (var "dir") (var "dirCheck"))
                          nothing
                          (var "adapter"))] $
                    Eithers.bind (var "getVertexId" @@ (inject PG._Direction PG._Direction_out unit) @@ var "outAdapter")
                      ("outId" ~> Eithers.bind (var "getVertexId" @@ (inject PG._Direction PG._Direction_in unit) @@ var "inAdapter")
                        ("inId" ~>
                          -- Find dependencies from vertex adapters
                          Eithers.bind (Eithers.map (lambda "xs" $ Maybes.cat (var "xs"))
                            (Eithers.mapList
                              ("va" ~> lets [
                                "fname">: Pairs.first $ var "va",
                                "ad">: Pairs.second $ var "va"] $
                                Maybes.maybe
                                  (right nothing)
                                  ("fterm" ~> Eithers.map (lambda "x" $ just (var "x"))
                                    (Coders.coderEncode (Coders.adapterCoder $ var "ad") @@ var "cx" @@ var "fterm"))
                                  (Maps.lookup (var "fname") (var "fieldsm")))
                              (var "vertexAdapters")))
                            ("deps" ~>
                              right (elementTreeEdge
                                @@ (record PG._Edge [
                                  PG._Edge_label>>: var "label",
                                  PG._Edge_id>>: var "edgeId",
                                  PG._Edge_out>>: var "outId",
                                  PG._Edge_in>>: var "inId",
                                  PG._Edge_properties>>: var "props"])
                                @@ var "deps"))))))))
        ("cx" ~> "_" ~> left (Error.errorOther $ Error.otherError $ string "edge decoding is not yet supported")))

-- | Create an edge id adapter
edgeIdAdapter :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> t -> Name -> Name -> [FieldType]
  -> Either Error (Y.Maybe (Name, Adapter Type t Term v)))
edgeIdAdapter = define "edgeIdAdapter" $
  doc "Create an edge id adapter" $
  "cx" ~> "g" ~> "schema" ~> "eidType" ~> "name" ~> "idKey" ~> "fields" ~>
    Eithers.bind (findIdProjectionSpec @@ var "cx" @@ false @@ var "name" @@ var "idKey" @@ var "fields")
      ("mIdSpec" ~> Maybes.maybe
        (right nothing)
        ("idSpec" ~> Eithers.map (lambda "x" $ just (var "x")) (projectionAdapter @@ var "cx" @@ var "g" @@ var "eidType" @@ (project PGM._Schema PGM._Schema_edgeIds @@ var "schema") @@ var "idSpec" @@ string "id"))
        (var "mIdSpec"))

-- | Construct an element adapter for a given type
elementCoder :: TTermDefinition (Y.Maybe (PG.Direction, PG.VertexLabel) -> PGM.Schema Graph t v -> Type -> t -> t -> Context -> Graph
  -> Either Error (Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v)))
elementCoder = define "elementCoder" $
  doc "Construct an element adapter for a given type, interpreting it either as a vertex specification or an edge specification" $
  "mparent" ~> "schema" ~> "source" ~> "vidType" ~> "eidType" ~> "cx" ~> "g" ~> lets [
    "dir">: Maybes.maybe (inject PG._Direction PG._Direction_both unit) (lambda "p" $ Pairs.first (var "p")) (var "mparent"),
    "parentLabel">: Maybes.maybe (wrap PG._VertexLabel $ string "NOLABEL") (lambda "p" $ Pairs.second (var "p")) (var "mparent")] $
    cases _Type (Strip.deannotateType @@ var "source")
      (Just $ unexpectedE (var "cx") (string "record type") (string "other type")) [
      _Type_maybe>>: "ot" ~>
        elementCoder @@ var "mparent" @@ var "schema" @@ var "ot" @@ var "vidType" @@ var "eidType" @@ var "cx" @@ var "g",
      _Type_record>>: "fields" ~> lets [
        "name">: Core.name (string "placeholder"),
        "outVertexKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_outVertex @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"),
        "outVertexLabelKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_outVertexLabel @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"),
        "inVertexKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_inVertex @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"),
        "inVertexLabelKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_inVertexLabel @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema")] $
        Eithers.bind (findProjectionSpec @@ var "cx" @@ var "g" @@ var "name" @@ var "outVertexKey" @@ var "outVertexLabelKey" @@ var "fields")
          ("mOutSpec" ~> Eithers.bind (findProjectionSpec @@ var "cx" @@ var "g" @@ var "name" @@ var "inVertexKey" @@ var "inVertexLabelKey" @@ var "fields")
            ("mInSpec" ~> lets [
              "kind">: Logic.ifElse (hasVertexAdapters @@ var "dir" @@ var "mOutSpec" @@ var "mInSpec")
                (inject PG._ElementKind PG._ElementKind_edge unit)
                (inject PG._ElementKind PG._ElementKind_vertex unit)] $
              Eithers.bind (findPropertySpecs @@ var "cx" @@ var "g" @@ var "schema" @@ var "kind" @@ var "fields")
                ("propSpecs" ~> Eithers.bind (Eithers.mapList (propertyAdapter @@ var "cx" @@ var "g" @@ var "schema") (var "propSpecs"))
                  ("propAdapters" ~>
                    match PG._ElementKind Nothing [
                      PG._ElementKind_vertex>>: constant $
                        constructVertexCoder @@ var "cx" @@ var "g" @@ var "schema" @@ var "source" @@ var "vidType" @@ var "eidType" @@ var "name" @@ var "fields" @@ var "propAdapters",
                      PG._ElementKind_edge>>: constant $
                        constructEdgeCoder @@ var "cx" @@ var "g" @@ var "parentLabel" @@ var "schema" @@ var "source" @@ var "vidType" @@ var "eidType" @@ var "dir" @@ var "name" @@ var "fields" @@ var "propAdapters" @@ var "mOutSpec" @@ var "mInSpec"]
                    @@ var "kind"))))]

-- | Create an element tree for an edge
elementTreeEdge :: TTermDefinition (PG.Edge v -> [PG.ElementTree v] -> PG.ElementTree v)
elementTreeEdge = define "elementTreeEdge" $
  doc "Create an element tree for an edge" $
  "edge" ~> "deps" ~>
    record PG._ElementTree [
      PG._ElementTree_self>>: inject PG._Element PG._Element_edge (var "edge"),
      PG._ElementTree_dependencies>>: var "deps"]

-- | Create an element tree for a vertex
elementTreeVertex :: TTermDefinition (PG.Vertex v -> [PG.ElementTree v] -> PG.ElementTree v)
elementTreeVertex = define "elementTreeVertex" $
  doc "Create an element tree for a vertex" $
  "vertex" ~> "deps" ~>
    record PG._ElementTree [
      PG._ElementTree_self>>: inject PG._Element PG._Element_vertex (var "vertex"),
      PG._ElementTree_dependencies>>: var "deps"]

-- | Create an element type tree for an edge type
elementTypeTreeEdge :: TTermDefinition (PG.EdgeType t -> [PG.ElementTypeTree t] -> PG.ElementTypeTree t)
elementTypeTreeEdge = define "elementTypeTreeEdge" $
  doc "Create an element type tree for an edge type" $
  "etype" ~> "deps" ~>
    record PG._ElementTypeTree [
      PG._ElementTypeTree_self>>: inject PG._ElementType PG._ElementType_edge (var "etype"),
      PG._ElementTypeTree_dependencies>>: var "deps"]

-- | Create an element type tree for a vertex type
elementTypeTreeVertex :: TTermDefinition (PG.VertexType t -> [PG.ElementTypeTree t] -> PG.ElementTypeTree t)
elementTypeTreeVertex = define "elementTypeTreeVertex" $
  doc "Create an element type tree for a vertex type" $
  "vtype" ~> "deps" ~>
    record PG._ElementTypeTree [
      PG._ElementTypeTree_self>>: inject PG._ElementType PG._ElementType_vertex (var "vtype"),
      PG._ElementTypeTree_dependencies>>: var "deps"]

-- | Encode all properties from a field map using property adapters
encodeProperties :: TTermDefinition (Context -> M.Map Name Term -> [Adapter FieldType (PG.PropertyType t) Field (PG.Property v)]
  -> Either Error (M.Map PG.PropertyKey v))
encodeProperties = define "encodeProperties" $
  doc "Encode all properties from a field map using property adapters" $
  "cx" ~> "fields" ~> "adapters" ~>
    Eithers.map
      ("props" ~> Maps.fromList (Lists.map
        ("prop" ~> pair (project PG._Property PG._Property_key @@ var "prop") (project PG._Property PG._Property_value @@ var "prop"))
        (var "props")))
      (Eithers.map (lambda "xs" $ Maybes.cat (var "xs")) (Eithers.mapList (encodeProperty @@ var "cx" @@ var "fields") (var "adapters")))

-- | Encode a single property from a field map using a property adapter
encodeProperty :: TTermDefinition (Context -> M.Map Name Term -> Adapter FieldType (PG.PropertyType t) Field (PG.Property v)
  -> Either Error (Y.Maybe (PG.Property v)))
encodeProperty = define "encodeProperty" $
  doc "Encode a single property from a field map using a property adapter" $
  "cx" ~> "fields" ~> "adapter" ~> lets [
    "fname">: Core.fieldTypeName $ (Coders.adapterSource $ var "adapter"),
    "ftyp">: Strip.deannotateType @@ (Core.fieldTypeType $ Coders.adapterSource $ var "adapter"),
    "isMaybe">: cases _Type (var "ftyp") (Just false) [
      _Type_maybe>>: constant true],
    "encodeValue">: "v" ~> Eithers.map (lambda "x" $ just (var "x"))
      (Coders.coderEncode (Coders.adapterCoder $ var "adapter") @@ var "cx" @@ (Core.field (var "fname") (var "v")))] $
    Maybes.maybe
      -- Field not found in record
      (Logic.ifElse (var "isMaybe")
        (right nothing)
        (err (var "cx") (string "expected field not found in record: " ++ (Core.unName $ var "fname"))))
      -- Field found in record
      ("value" ~>
        Logic.ifElse (var "isMaybe")
          -- Optional field: unwrap TermMaybe
          (cases _Term (Strip.deannotateTerm @@ var "value") (Just $ var "encodeValue" @@ var "value") [
            _Term_maybe>>: "ov" ~>
              Maybes.maybe (right nothing) (var "encodeValue") (var "ov")])
          -- Required field: encode directly
          (var "encodeValue" @@ var "value"))
      (Maps.lookup (var "fname") (var "fields"))

-- | Bridge an ExtractCore function into Result
extractString :: TTermDefinition (Context -> Graph -> Term -> Either Error String)
extractString = define "extractString" $
  doc "Extract a string from a term" $
  "cx" ~> "g" ~> "t" ~>
    ExtractCore.string @@ var "g" @@ var "t"

-- | Find adjacent edge adapters for a given direction
findAdjacenEdgeAdapters :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> t -> t -> PG.VertexLabel -> PG.Direction -> [FieldType]
  -> Either Error [(PG.Direction, FieldType, PG.EdgeLabel, Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v))])
findAdjacenEdgeAdapters = define "findAdjacenEdgeAdapters" $
  doc "Find adjacent edge adapters for a given direction" $
  "cx" ~> "g" ~> "schema" ~> "vidType" ~> "eidType" ~> "parentLabel" ~> "dir" ~> "fields" ~>
    Eithers.map (lambda "xs" $ Maybes.cat (var "xs")) (Eithers.mapList
      ("field" ~> lets [
        "key">: Core.name $ match PG._Direction Nothing [
          PG._Direction_out>>: constant $ project PGM._AnnotationSchema PGM._AnnotationSchema_outEdgeLabel @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"),
          PG._Direction_in>>: constant $ project PGM._AnnotationSchema PGM._AnnotationSchema_inEdgeLabel @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema")]
          @@ var "dir"] $
        Maybes.maybe
          (right nothing)
          ("a" ~> Eithers.bind (extractString @@ var "cx" @@ var "g" @@ var "a")
            ("labelStr" ~> Eithers.bind (elementCoder @@ (just (pair (var "dir") (var "parentLabel"))) @@ var "schema" @@ (Core.fieldTypeType $ var "field") @@ var "vidType" @@ var "eidType" @@ var "cx" @@ var "g")
              ("elad" ~> right (just (tuple4 (var "dir") (var "field") (wrap PG._EdgeLabel $ var "labelStr") (var "elad"))))))
          (Annotations.getTypeAnnotation @@ var "key" @@ (Core.fieldTypeType $ var "field")))
      (var "fields"))

-- | Find an id projection spec for a field
findIdProjectionSpec :: TTermDefinition (Context -> Bool -> Name -> Name -> [FieldType]
  -> Either Error (Y.Maybe (FieldType, PGM.ValueSpec, Y.Maybe String)))
findIdProjectionSpec = define "findIdProjectionSpec" $
  doc "Find an id projection spec for a field" $
  "cx" ~> "required" ~> "tname" ~> "idKey" ~> "fields" ~>
    Eithers.bind (findSingleFieldWithAnnotationKey @@ var "cx" @@ var "tname" @@ var "idKey" @@ var "fields")
      ("mid" ~> Maybes.maybe
        (Logic.ifElse (var "required")
          (err (var "cx") (string "no " ++ (Core.unName $ var "idKey") ++ string " field"))
          (right nothing))
        ("mi" ~> Eithers.map
          ("spec" ~> just (pair (var "mi") (pair (var "spec")
            (Maybes.map ("s" ~> Strings.toUpper (var "s")) nothing))))
          (Maybes.maybe
            (right (inject PGM._ValueSpec PGM._ValueSpec_value unit))
            (TermsToElements.decodeValueSpec @@ var "cx" @@ Graph.emptyGraph)
            (Annotations.getTypeAnnotation @@ var "idKey" @@ (Core.fieldTypeType $ var "mi"))))
        (var "mid"))

-- | Find an incident vertex adapter for a projection spec
findIncidentVertexAdapter :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> t -> t -> (FieldType, PGM.ValueSpec, Y.Maybe String)
  -> Either Error (Name, Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v)))
findIncidentVertexAdapter = define "findIncidentVertexAdapter" $
  doc "Find an incident vertex adapter for a projection spec" $
  "cx" ~> "g" ~> "schema" ~> "vidType" ~> "eidType" ~> "spec" ~> lets [
    "field">: Pairs.first $ var "spec"] $
    Eithers.bind (elementCoder @@ nothing @@ var "schema" @@ (Core.fieldTypeType $ var "field") @@ var "vidType" @@ var "eidType" @@ var "cx" @@ var "g")
      ("adapter" ~> right (pair (Core.fieldTypeName $ var "field") (var "adapter")))

-- | Find a label string from annotations or the type name
findLabelString :: TTermDefinition (Context -> Graph -> Type -> Name -> Name -> Either Error String)
findLabelString = define "findLabelString" $
  doc "Find a label string from annotations or the type name" $
  "cx" ~> "g" ~> "source" ~> "tname" ~> "labelKey" ~>
    Maybes.maybe
      (right $ Core.unName $ var "tname")
      (extractString @@ var "cx" @@ var "g")
      (Annotations.getTypeAnnotation @@ var "labelKey" @@ var "source")

-- | Find a projection spec for a field
findProjectionSpec :: TTermDefinition (Context -> Graph -> Name -> Name -> Name -> [FieldType]
  -> Either Error (Y.Maybe (FieldType, PGM.ValueSpec, Y.Maybe String)))
findProjectionSpec = define "findProjectionSpec" $
  doc "Find a projection spec for a field" $
  "cx" ~> "g" ~> "tname" ~> "key" ~> "aliasKey" ~> "fields" ~>
    Eithers.bind (findSingleFieldWithAnnotationKey @@ var "cx" @@ var "tname" @@ var "key" @@ var "fields")
      ("mfield" ~> Maybes.maybe
        (right nothing)
        ("field" ~>
          Maybes.maybe
            (left $ Error.errorOther $ Error.otherError $ string "findProjectionSpec: missing type annotation for key")
            ("annot" ~>
              Eithers.bind (TermsToElements.decodeValueSpec @@ var "cx" @@ var "g" @@ var "annot")
                ("spec" ~>
                  Eithers.bind (Maybes.maybe (right nothing) ("t" ~> Eithers.map (lambda "x" $ just (var "x")) (extractString @@ var "cx" @@ var "g" @@ var "t"))
                    (Annotations.getTypeAnnotation @@ var "aliasKey" @@ (Core.fieldTypeType $ var "field")))
                    ("alias" ~> right (just (pair (var "field") (pair (var "spec") (var "alias")))))))
            (Annotations.getTypeAnnotation @@ var "key" @@ (Core.fieldTypeType $ var "field")))
        (var "mfield"))

-- | Find property specs for element fields
findPropertySpecs :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> PG.ElementKind -> [FieldType]
  -> Either Error [(FieldType, PGM.ValueSpec, Y.Maybe String)])
findPropertySpecs = define "findPropertySpecs" $
  doc "Find property specs for element fields" $
  "cx" ~> "g" ~> "schema" ~> "kind" ~> "fields" ~>
    Eithers.mapList
      ("field" ~> lets [
        "propKeyKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_propertyKey @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema"),
        "propValueKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_propertyValue @@ (project PGM._Schema PGM._Schema_annotations @@ var "schema")] $
        Eithers.bind (Maybes.maybe (right nothing) ("a" ~> Eithers.map (lambda "x" $ just (var "x")) (extractString @@ var "cx" @@ var "g" @@ var "a"))
          (Annotations.getTypeAnnotation @@ var "propKeyKey" @@ (Core.fieldTypeType $ var "field")))
          ("alias" ~> Eithers.bind (Maybes.maybe (right (inject PGM._ValueSpec PGM._ValueSpec_value unit)) (TermsToElements.decodeValueSpec @@ var "cx" @@ var "g")
            (Annotations.getTypeAnnotation @@ var "propValueKey" @@ (Core.fieldTypeType $ var "field")))
            ("values" ~> right (pair (var "field") (pair (var "values") (var "alias"))))))
      (Lists.filter
        ("field" ~> lets [
          "annots">: project PGM._Schema PGM._Schema_annotations @@ var "schema",
          "ignoreKey">: Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_ignore @@ var "annots",
          "specialKeys">: match PG._ElementKind Nothing [
            PG._ElementKind_vertex>>: constant $ list [
              Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_vertexId @@ var "annots",
              Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_outEdgeLabel @@ var "annots",
              Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_inEdgeLabel @@ var "annots"],
            PG._ElementKind_edge>>: constant $ list [
              Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_edgeId @@ var "annots",
              Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_outVertex @@ var "annots",
              Core.name $ project PGM._AnnotationSchema PGM._AnnotationSchema_inVertex @@ var "annots"]]
            @@ var "kind",
          "allKeys">: Lists.concat $ list [list [var "ignoreKey"], var "specialKeys"],
          "hasSpecialAnnotation">: Lists.foldl
            ("b" ~> "k" ~> Logic.or (var "b") (Maybes.isJust (Annotations.getTypeAnnotation @@ var "k" @@ (Core.fieldTypeType $ var "field"))))
            false
            (var "allKeys"),
          "hasSpecialFieldName">: Lists.foldl
            ("b" ~> "k" ~> Logic.or (var "b") (Equality.equal (Core.fieldTypeName $ var "field") (var "k")))
            false
            (var "specialKeys")] $
          Logic.not (Logic.or (var "hasSpecialAnnotation") (var "hasSpecialFieldName")))
        (var "fields"))

-- | Find a single field with a given annotation key
findSingleFieldWithAnnotationKey :: TTermDefinition (Context -> Name -> Name -> [FieldType]
  -> Either Error (Y.Maybe FieldType))
findSingleFieldWithAnnotationKey = define "findSingleFieldWithAnnotationKey" $
  doc "Find a single field with a given annotation key" $
  "cx" ~> "tname" ~> "key" ~> "fields" ~> lets [
    "matches">: Lists.filter
      ("f" ~> Maybes.isJust (Annotations.getTypeAnnotation @@ var "key" @@ (Core.fieldTypeType $ var "f")))
      (var "fields")] $
    Logic.ifElse (Equality.gt (Lists.length $ var "matches") (int32 1))
      (err (var "cx") (string "Multiple fields marked as '" ++ (Core.unName $ var "key") ++ string "' in record type " ++ (Core.unName $ var "tname")))
      (right (Lists.maybeHead $ var "matches"))

-- | Determine whether the spec has vertex adapters based on direction and out/in specs
hasVertexAdapters :: TTermDefinition (PG.Direction -> Y.Maybe a -> Y.Maybe b -> Bool)
hasVertexAdapters = define "hasVertexAdapters" $
  doc "Determine whether the spec has vertex adapters based on direction and out/in specs" $
  "dir" ~> "mOutSpec" ~> "mInSpec" ~>
    match PG._Direction Nothing [
      PG._Direction_out>>: constant $ Maybes.isJust (var "mInSpec"),
      PG._Direction_in>>: constant $ Maybes.isJust (var "mOutSpec"),
      PG._Direction_both>>: constant $ Logic.and (Maybes.isJust (var "mOutSpec")) (Maybes.isJust (var "mInSpec"))]
    @@ var "dir"

-- | Create a projection adapter from a projection spec
projectionAdapter :: TTermDefinition (Context -> Graph -> t -> Coder Term v -> (FieldType, PGM.ValueSpec, Y.Maybe String) -> String
  -> Either Error (Name, Adapter Type t Term v))
projectionAdapter = define "projectionAdapter" $
  doc "Create a projection adapter from a projection spec" $
  "cx" ~> "g" ~> "idtype" ~> "coder" ~> "spec" ~> "key" ~> lets [
    "field">: Pairs.first $ var "spec",
    "values">: Pairs.first $ Pairs.second $ var "spec"] $
    Eithers.bind (TermsToElements.parseValueSpec @@ var "cx" @@ var "g" @@ var "values")
      ("traversal" ~> right (pair
        (Core.fieldTypeName $ var "field")
        (Coders.adapter true (Core.fieldTypeType $ var "field") (var "idtype")
          (Coders.coder
            ("cx'" ~> "typ" ~>
              Eithers.bind (traverseToSingleTerm @@ var "cx'" @@ (var "key" ++ string "-projection") @@ (var "traversal" @@ var "cx'") @@ var "typ")
                ("t" ~> Coders.coderEncode (var "coder") @@ var "cx'" @@ var "t"))
            ("cx'" ~> "_" ~> left (Error.errorOther $ Error.otherError $ string "edge '" ++ var "key" ++ string "' decoding is not yet supported"))))))

-- | Create a property adapter from a property spec
propertyAdapter :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> (FieldType, PGM.ValueSpec, Y.Maybe String)
  -> Either Error (Adapter FieldType (PG.PropertyType t) Field (PG.Property v)))
propertyAdapter = define "propertyAdapter" $
  doc "Create a property adapter from a property spec" $
  "cx" ~> "g" ~> "schema" ~> "spec" ~> lets [
    "tfield">: Pairs.first $ var "spec",
    "values">: Pairs.first $ Pairs.second $ var "spec",
    "alias">: Pairs.second $ Pairs.second $ var "spec",
    "key">: wrap PG._PropertyKey $ (Maybes.fromMaybe (Core.unName $ (Core.fieldTypeName $ var "tfield")) (var "alias"))] $
    Eithers.bind (Coders.coderEncode (project PGM._Schema PGM._Schema_propertyTypes @@ var "schema") @@ var "cx" @@ (Core.fieldTypeType $ var "tfield"))
      ("pt" ~> Eithers.bind (TermsToElements.parseValueSpec @@ var "cx" @@ var "g" @@ var "values")
        ("traversal" ~> right
          (Coders.adapter true (var "tfield")
            (record PG._PropertyType [
              PG._PropertyType_key>>: var "key",
              PG._PropertyType_value>>: var "pt",
              PG._PropertyType_required>>: true])
            (Coders.coder
              ("cx'" ~> "dfield" ~>
                Eithers.bind (traverseToSingleTerm @@ var "cx'" @@ string "property traversal" @@ (var "traversal" @@ var "cx'") @@ (Core.fieldTerm $ var "dfield"))
                  ("result" ~> Eithers.bind (Coders.coderEncode (project PGM._Schema PGM._Schema_propertyValues @@ var "schema") @@ var "cx'" @@ var "result")
                    ("value" ~> right (record PG._Property [
                      PG._Property_key>>: var "key",
                      PG._Property_value>>: var "value"]))))
              ("cx'" ~> "_" ~> left (Error.errorOther $ Error.otherError $ string "property decoding is not yet supported"))))))

-- | Extract property types from property adapters
propertyTypes :: TTermDefinition ([Adapter FieldType (PG.PropertyType t) Field (PG.Property v)] -> [PG.PropertyType t])
propertyTypes = define "propertyTypes" $
  doc "Extract property types from property adapters" $
  "propAdapters" ~>
    Lists.map
      ("a" ~> record PG._PropertyType [
        PG._PropertyType_key>>: project PG._PropertyType PG._PropertyType_key @@ (Coders.adapterTarget $ var "a"),
        PG._PropertyType_value>>: project PG._PropertyType PG._PropertyType_value @@ (Coders.adapterTarget $ var "a"),
        PG._PropertyType_required>>: true])
      (var "propAdapters")

-- | Select an edge id from record fields using an id adapter
selectEdgeId :: TTermDefinition (Context -> M.Map Name Term -> (Name, Adapter Type t Term v) -> Either Error v)
selectEdgeId = define "selectEdgeId" $
  doc "Select an edge id from record fields using an id adapter" $
  "cx" ~> "fields" ~> "ad" ~> lets [
    "fname">: Pairs.first $ var "ad",
    "adapter">: Pairs.second $ var "ad"] $
    Maybes.maybe
      (err (var "cx") (string "no " ++ (Core.unName $ var "fname") ++ string " in record"))
      ("t" ~> Coders.coderEncode (Coders.adapterCoder $ var "adapter") @@ var "cx" @@ var "t")
      (Maps.lookup (var "fname") (var "fields"))

-- | Select a vertex id from record fields using an id adapter
selectVertexId :: TTermDefinition (Context -> M.Map Name Term -> (Name, Adapter Type t Term v) -> Either Error v)
selectVertexId = define "selectVertexId" $
  doc "Select a vertex id from record fields using an id adapter" $
  "cx" ~> "fields" ~> "ad" ~> lets [
    "fname">: Pairs.first $ var "ad",
    "adapter">: Pairs.second $ var "ad"] $
    Maybes.maybe
      (err (var "cx") (string "no " ++ (Core.unName $ var "fname") ++ string " in record"))
      ("t" ~> Coders.coderEncode (Coders.adapterCoder $ var "adapter") @@ var "cx" @@ var "t")
      (Maps.lookup (var "fname") (var "fields"))

-- | Traverse to a single term, failing if zero or multiple terms are found
traverseToSingleTerm :: TTermDefinition (Context -> String -> (Term -> Either Error [Term]) -> Term -> Either Error Term)
traverseToSingleTerm = define "traverseToSingleTerm" $
  doc "Traverse to a single term, failing if zero or multiple terms are found" $
  "cx" ~> "desc" ~> "traversal" ~> "term" ~>
    Eithers.bind (var "traversal" @@ var "term")
      ("terms" ~>
        Logic.ifElse (Lists.null $ var "terms")
          (err (var "cx") (var "desc" ++ string " did not resolve to a term"))
          (Logic.ifElse (Equality.equal (Lists.length $ var "terms") (int32 1))
            (Maybes.maybe
              (err (var "cx") (var "desc" ++ string " resolved to multiple terms"))
              (unaryFunction right)
              (Lists.maybeHead $ var "terms"))
            (err (var "cx") (var "desc" ++ string " resolved to multiple terms"))))

-- | Create a vertex coder given all components
vertexCoder :: TTermDefinition (Graph -> PGM.Schema Graph t v -> Type -> t -> Name -> PG.VertexLabel
  -> (Name, Adapter Type t Term v)
  -> [Adapter FieldType (PG.PropertyType t) Field (PG.Property v)]
  -> [(PG.Direction, FieldType, PG.EdgeLabel, Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v))]
  -> Adapter Type (PG.ElementTypeTree t) Term (PG.ElementTree v))
vertexCoder = define "vertexCoder" $
  doc "Create a vertex coder given all components" $
  "g" ~> "schema" ~> "source" ~> "vidType" ~> "tname" ~> "vlabel" ~>
  "idAdapter" ~> "propAdapters" ~> "edgeAdapters" ~> lets [
    "vtype">: record PG._VertexType [
      PG._VertexType_label>>: var "vlabel",
      PG._VertexType_id>>: var "vidType",
      PG._VertexType_properties>>: propertyTypes @@ var "propAdapters"],
    "depTypes">: Lists.map
      ("ea" ~> Coders.adapterTarget $ (Pairs.second $ Pairs.second $ Pairs.second $ var "ea"))
      (var "edgeAdapters"),
    "target">: elementTypeTreeVertex @@ var "vtype" @@ var "depTypes"] $
    Coders.adapter true (var "source") (var "target")
      (Coders.coder
        ("cx" ~> "term" ~>
          lets ["deannot">: Strip.deannotateTerm @@ var "term",
                -- Unwrap TermMaybe if present
                "unwrapped">: cases _Term (var "deannot") (Just $ var "deannot") [
                  _Term_maybe>>: "mt" ~> Maybes.fromMaybe (var "deannot") (var "mt")],
                "rec">: cases _Term (var "unwrapped") Nothing [
                  _Term_record>>: lambda "r" $ var "r"],
                "fmap">: Resolution.fieldMap @@ (Core.recordFields $ var "rec")] $
          Eithers.bind (selectVertexId @@ var "cx" @@ var "fmap" @@ var "idAdapter")
            ("vid" ~> Eithers.bind (encodeProperties @@ var "cx" @@ var "fmap" @@ var "propAdapters")
              ("props" ~>
                -- Find dependencies from edge adapters
                Eithers.bind (Eithers.map (lambda "xs" $ Lists.concat (var "xs"))
                  (Eithers.mapList
                    ("ea" ~> lets [
                      "eaDir">: Pairs.first $ var "ea",
                      "eaField">: Pairs.first $ Pairs.second $ var "ea",
                      "eaLabel">: Pairs.first $ Pairs.second $ Pairs.second $ var "ea",
                      "eaAdapter">: Pairs.second $ Pairs.second $ Pairs.second $ var "ea"] $
                      Maybes.maybe
                        (right (list ([] :: [TTerm (PG.ElementTree v)])))
                        ("fterm" ~> Eithers.map
                          ("tree" ~>
                            -- fixTree: inspect element tree self
                            match PG._Element Nothing [
                              PG._Element_vertex>>: "vtx" ~> lets [
                                -- Child is a vertex: create an edge connecting parent to child
                                "otherid">: project PG._Vertex PG._Vertex_id @@ var "vtx",
                                "edgeid">: project PGM._Schema PGM._Schema_defaultEdgeId @@ var "schema",
                                "outId">: match PG._Direction Nothing [
                                  PG._Direction_out>>: constant $ var "vid",
                                  PG._Direction_in>>: constant $ var "otherid"]
                                  @@ var "eaDir",
                                "inId">: match PG._Direction Nothing [
                                  PG._Direction_out>>: constant $ var "otherid",
                                  PG._Direction_in>>: constant $ var "vid"]
                                  @@ var "eaDir",
                                "edge">: inject PG._Element PG._Element_edge (record PG._Edge [
                                  PG._Edge_label>>: var "eaLabel",
                                  PG._Edge_id>>: var "edgeid",
                                  PG._Edge_out>>: var "outId",
                                  PG._Edge_in>>: var "inId",
                                  PG._Edge_properties>>: Maps.empty])] $
                                list [record PG._ElementTree [
                                  PG._ElementTree_self>>: var "edge",
                                  PG._ElementTree_dependencies>>: list [var "tree"]]],
                              PG._Element_edge>>: "edg" ~> lets [
                                -- Child is an edge: fix its out/in vertex id
                                "fixedEdge">: match PG._Direction Nothing [
                                  PG._Direction_out>>: constant $ record PG._Edge [
                                    PG._Edge_label>>: project PG._Edge PG._Edge_label @@ var "edg",
                                    PG._Edge_id>>: project PG._Edge PG._Edge_id @@ var "edg",
                                    PG._Edge_out>>: var "vid",
                                    PG._Edge_in>>: project PG._Edge PG._Edge_in @@ var "edg",
                                    PG._Edge_properties>>: project PG._Edge PG._Edge_properties @@ var "edg"],
                                  PG._Direction_in>>: constant $ record PG._Edge [
                                    PG._Edge_label>>: project PG._Edge PG._Edge_label @@ var "edg",
                                    PG._Edge_id>>: project PG._Edge PG._Edge_id @@ var "edg",
                                    PG._Edge_out>>: project PG._Edge PG._Edge_out @@ var "edg",
                                    PG._Edge_in>>: var "vid",
                                    PG._Edge_properties>>: project PG._Edge PG._Edge_properties @@ var "edg"]]
                                  @@ var "eaDir"] $
                                list [record PG._ElementTree [
                                  PG._ElementTree_self>>: inject PG._Element PG._Element_edge (var "fixedEdge"),
                                  PG._ElementTree_dependencies>>: project PG._ElementTree PG._ElementTree_dependencies @@ var "tree"]]]
                            @@ (project PG._ElementTree PG._ElementTree_self @@ var "tree"))
                          (Coders.coderEncode (Coders.adapterCoder $ var "eaAdapter") @@ var "cx" @@ var "fterm"))
                        (Maps.lookup (Core.fieldTypeName $ var "eaField") (var "fmap")))
                    (var "edgeAdapters")))
                  ("deps" ~>
                    right (elementTreeVertex
                      @@ (record PG._Vertex [
                        PG._Vertex_label>>: var "vlabel",
                        PG._Vertex_id>>: var "vid",
                        PG._Vertex_properties>>: var "props"])
                      @@ var "deps")))))
        ("cx" ~> "_" ~> left (Error.errorOther $ Error.otherError $ string "vertex decoding is not yet supported")))

-- | Create a vertex id adapter
vertexIdAdapter :: TTermDefinition (Context -> Graph -> PGM.Schema Graph t v -> t -> Name -> Name -> [FieldType]
  -> Either Error (Name, Adapter Type t Term v))
vertexIdAdapter = define "vertexIdAdapter" $
  doc "Create a vertex id adapter" $
  "cx" ~> "g" ~> "schema" ~> "vidType" ~> "name" ~> "idKey" ~> "fields" ~>
    Eithers.bind (findIdProjectionSpec @@ var "cx" @@ true @@ var "name" @@ var "idKey" @@ var "fields")
      ("mIdSpec" ~> Maybes.maybe
        (left $ Error.errorOther $ Error.otherError $ string "vertexIdAdapter: no id projection spec")
        ("idSpec" ~> projectionAdapter @@ var "cx" @@ var "g" @@ var "vidType" @@ (project PGM._Schema PGM._Schema_vertexIds @@ var "schema") @@ var "idSpec" @@ string "id")
        (var "mIdSpec"))