Last few years I've been working on a IETF document that describes an EPP extension for transporting DNSSEC key material using the EPP protocol (See the datatracker from the IETF for more information. I've also been learning Erlang for the last 2,5 years. One of the first projects I wanted to do was validating EPP XML files against their XML schema in Erlang; Just for fun.

So attempt #1 started with xmerl and somehow got stuck on target namespace resolving. The thing is that the EPP XSD files contain namespace includes and somehow I didn't get it going. Then I came accross erlsom but that had another namespace resolving issue. But with a little tweaking of the EPP XSDs I got it working.

But tweaking IANA registered XSD files always felt bad and this bad feeling kept haunting me. So 2 years later whith more erlang expierence and a fresh cup of coffee I started with attemtpt #3: xmerl again. failure again. Somehow I keep ending up with a target namespace mismatch error. Tweaking XSDs didn't work :-(

Back to the coffee machine again and attempt #4 erlsom again ; succes

So the code below actually works with the unmodified IANA registered XSDs. It is copied from a rebar3 project and runs as a eunit test... I don't care, it works \0/

-ifdef(TEST).
epp_validation_test() ->
  PrivDir = code:priv_dir(xmlvalidator),
  XsdDir = PrivDir ++ "/xsd" ,
  IncludeDirs = [XsdDir],
  {ok, EppcomModel} = erlsom:compile_xsd_file(XsdDir ++ "/eppcom-1.0.xsd", [{prefix, "eppcom"},
    {include_dirs, IncludeDirs}]),

  {ok, SecDNSModel} = erlsom:add_xsd_file(XsdDir ++ "/secDNS-1.1.xsd", [{prefix, "secDNS"},
    {include_dirs, IncludeDirs}],
    EppcomModel),

  {ok, EppModel} = erlsom:add_xsd_file(XsdDir ++ "/epp-1.0.xsd", [{prefix, "epp"},
    {include_dirs, IncludeDirs},
    {include_files,[
      {"urn:ietf:params:xml:ns:eppcom-1.0","eppcom",XsdDir ++ "/eppcom-1.0.xsd"}
    ]}
  ], SecDNSModel),

  {ok, HostModel} = erlsom:add_xsd_file(XsdDir ++ "/host-1.0.xsd", [{prefix, "host"},
    {include_dirs, IncludeDirs},
    {include_files,[
      {"urn:ietf:params:xml:ns:eppcom-1.0","eppcom",XsdDir ++ "/eppcom-1.0.xsd"},
      {"urn:ietf:params:xml:ns:epp-1.0","epp",XsdDir ++ "/epp-1.0.xsd"}
    ]}
    ], EppModel),

  {ok, ContactModel} = erlsom:add_xsd_file(XsdDir ++ "/contact-1.0.xsd", [{prefix, "contact"},
    {include_dirs, IncludeDirs},
    {include_files,[
      {"urn:ietf:params:xml:ns:eppcom-1.0","eppcom",XsdDir ++ "/eppcom-1.0.xsd"},
      {"urn:ietf:params:xml:ns:epp-1.0","epp",XsdDir ++ "/epp-1.0.xsd"}
      ]}
    ], HostModel),

  {ok, DomainModel} = erlsom:add_xsd_file(XsdDir ++ "/domain-1.0.xsd", [{prefix, "domain"},
    {include_dirs, IncludeDirs},
    {include_files,[
      {"urn:ietf:params:xml:ns:eppcom-1.0","eppcom", XsdDir ++ "/eppcom-1.0.xsd"},
      {"urn:ietf:params:xml:ns:epp-1.0","epp", XsdDir ++ "/epp-1.0.xsd"},
      {"urn:ietf:params:xml:ns:host-1.0","host", XsdDir ++ "/host-1.0.xsd"}
    ]}
  ], ContactModel),

  {ok, KeyrelayModel} = erlsom:add_xsd_file(XsdDir ++ "/keyrelay-1.0.xsd", [{prefix, "keyrelay"},
    {include_dirs, IncludeDirs},
    {include_files,[
      {"urn:ietf:params:xml:ns:eppcom-1.0","eppcom", XsdDir ++ "/eppcom-1.0.xsd"},
      {"urn:ietf:params:xml:ns:epp-1.0","epp", XsdDir ++ "/epp-1.0.xsd"},
      {"urn:ietf:params:xml:ns:host-1.0","host", XsdDir ++ "/host-1.0.xsd"},
      {"urn:ietf:params:xml:ns:domain-1.0","domain", XsdDir ++ "/domain-1.0.xsd"},
      {"urn:ietf:params:xml:ns:secDNS-1.1","secDNS", XsdDir ++ "/secDNS-1.1.xsd"}
  ]}
  ], DomainModel),

  TheModel = KeyrelayModel,

  validate(filename:join([PrivDir, "epp_host_create.xml"]),TheModel),
  validate(filename:join([PrivDir, "epp_domain_create.xml"]),TheModel),
  validate(filename:join([PrivDir, "epp_contact_create.xml"]),TheModel),
  validate(filename:join([PrivDir, "epp_keyrelay_create.xml"]),TheModel),
  validate(filename:join([PrivDir, "epp_keyrelay_create_response.xml"]),TheModel),
  validate(filename:join([PrivDir, "epp_keyrelay_poll_response.xml"]),TheModel).


validate(File,Model) ->
  {Ok, _, _} = erlsom:scan_file(File, Model),
  ?assertEqual(ok, Ok).

-endif.