Writing an OData parser - starting at the beginning

Published on 2013-4-5

I'm going off what is specified in both the OData spec and the OData URI conventions document.

This is a bit annoying, because it seems like URI conventions are just that, conventions - and people are free to do what they want (I haven't looked at the metadata spec yet so I'm not sure how discoverable this customisability is, I guess I'll get there during my time on this task)

What I think I can start with, is parsing the following basics

How I'll develop this

This is yet another task I'll probably write tests for as I go so I can document how far I've gotten and have a safety net as I no doubt make lots of mistakes.

I'll copy and paste code from the old OData parser as I need it and as I write the tests to support it, in this even the legacy code will end up with coverage.

In this way, I hope to be able to hand this over to Rulemotion in its semi-complete state but with a nice document (the tests) explaining what is covered so far.

My first few tests

I'll not bother covering the order in which I do this, as it's pretty similar to how I did the JSON parser, except I can make a few more assumptions because I know a but more about how OMeta works.

function test(input, entry, expectation) {
  describe("Parsing " + input, function() {
    var parser = ODataParser.createInstance()
    var result = parser.matchAll(input, entry)
    expectation(result)
  });
}

test("/", "OData", function(result) {
  it("Service root should have no model", function() {
     assert.equal(result.resource, null)
  })
})

test("/model", "OData", function(result) {
  it("should have the resource specified", function() {
     assert.equal(result.resource, 'model')
  })
})

test("/model(1)", "OData", function(result) {
  it("should have the resource specified", function() {
     assert.equal(result.resource, 'model')
  })
  it("should have the key specified for the source", function() {
     assert.equal(result.key, '1')
  })
})

After the first couple of tests, setting up the parser etc was a ball-ache so I fixed it.

I'm missing out the bit where I specify what the service root is, I'll come back to it later as I'm more interested in parsing the path itself.

This is what I wrote to support the above tests:

ometa ODataParser 
  Number = <digit+>:d -> parseInt(d, 10),

  OData = (
      PathSegment
    | '/'
  ) 
  ,

  PathSegment = 
      '/'
        ResourceName:resource
        (
          ("(" Number:key ")")?
        ) -> { resource: resource, key: key }
      ,

  ResourcePart =
    <    (    letter
      |    '_'
      )+
    >:resourcePart
    -> resourcePart.replace(new RegExp('_', 'g'), ' '),

  ResourceName =
    <    ResourcePart
      (    '-'
        ResourcePart
      )*
    >
}

Things of note

This is all very rudimentary, now - looking at the URI conventions, they seem to support arbitrary paths into object relationships like so:

/model(1)/children(1)/otherchildren(1)/field

This suggests I probably want to recurse in order to build up this sequence

test("/model(1)/child", "OData", function(result) {
  it("should have the resource specified", function() {
     assert.equal(result.resource, 'model')
  })
  it("should have the key specified for the resource", function() {
     assert.equal(result.key, '1')
  })
  it("should have the child specified", function() {
     assert.equal(result.resource.next, 'child')
  })
})

Not sure if this is an appropriate representation, but it'll do for now until I find out how we're going to be consuming this model.

Having just arrived at the hotel and written this all on the boat, I'll defer having a look at how to do this until tomorrow, I've already passed the balmers peak.

2020 © Rob Ashton. ALL Rights Reserved.