HalBuilder / halbuilder-core

HalBuilder Core
38 stars 26 forks source link

withLink() always creates an array? #41

Closed dmolesUC closed 6 years ago

dmolesUC commented 6 years ago

Given the following:

    RepresentationFactory rf = new StandardRepresentationFactory()
      .withFlag(RepresentationFactory.PRETTY_PRINT);
    return rf.newRepresentation("/orders")
      .withNamespace("acme", "http://docs.acme.com/relations/{rel}")
      .withLink("acme:widgets", "/widgets")
      .toString(RepresentationFactory.HAL_JSON);

I expected to get a single object for the acme:widgets link, thus:

{
  "_links" : {
    "curies" : [ {
      "href" : "http://docs.acme.com/relations/{rel}",
      "name" : "acme",
      "templated" : true
    } ],
    "self" : {
      "href" : "/orders"
    },
    "acme:widgets" : {
      "href" : "/widgets"
    }
  }
}

(Cf. the example in Section 8.2, "Link Relations", of the HAL Internet Draft, which is identical apart from more compact formatting and swapping the order of the self and curies properties.)

Instead, I get acme:widgets as a single-element array:

{
  "_links" : {
    "curies" : [ {
      "href" : "http://docs.acme.com/relations/{rel}",
      "name" : "acme",
      "templated" : true
    } ],
    "self" : {
      "href" : "/orders"
    },
    "acme:widgets" : [ {
      "href" : "/widgets"
    } ]
  }
}

How do I create a singular link?

dmolesUC commented 6 years ago

Digging through the source code, I found the RepresentationFactory.COALESCE_ARRAYS flag, but this coalesces all link arrays:

{
  "_links" : {
    "curies" : {
      "href" : "https://github.com/dmolesUC3/cursive/blob/master/RELATIONS.md#{rel}",
      "name" : "cursive",
      "templated" : true
    },
    "self" : {
      "href" : "/"
    },
    "cursive:workspaces" : {
      "href" : "workspaces"
    }
  }
}

It seems like there should be some way to set this on a per-relation basis, but there doesn't seem to be, at least in HalBuilder 4.0.x.

I've been using 4.0.1 since that's what's documented in the User Guide, but I see now there's a HalBuilder 5 which seems to have a concept of a singleton relation. I'll try that and see if I get anywhere.

dmolesUC commented 6 years ago

OK, here's the HalBuilder 5 singleton version, which works:

    Map<String, Object> properties = Collections.emptyMap();
    Rel workspacesRel = Rels.singleton("cursive:workspaces");
    ResourceRepresentation<Map<String, Object>> rep = 
      ResourceRepresentation.create("/", properties)
        .withNamespace("cursive",
          "https://github.com/dmolesUC3/cursive/blob/master/RELATIONS.md#{rel}")
      .withRel(workspacesRel)
      .withLink(workspacesRel.rel(), "workspaces");

produces:

{
  "_links" : {
    "curies" : [ {
      "href" : "https://github.com/dmolesUC3/cursive/blob/master/RELATIONS.md#{rel}",
      "name" : "cursive",
      "templated" : true
    } ],
    "self" : {
      "href" : "/"
    },
    "cursive:workspaces" : {
      "href" : "workspaces"
    }
  }
}
talios commented 6 years ago

You should be able to just use .withLink(workspacesRel, "workspaces"); as well, if not - that's an oversight on my part.

dmolesUC commented 6 years ago

Yep, there doesn't seem to be a withLink(Rel, String). That would be nice to have.

talios commented 6 years ago

I had a quick look at it - kinda nasty in that adding it will add 5-6 variations to overload. I remember one reason I initially didn't use it was an internal question over to how to handle things like:

res
  .withLink(Rel.singleton("bar"), "....")
  .withLink(Rel.collection("bar"), "....");

where different instances using the same rel string are provided. I think I know what I want to do there tho now. Check if the Rel instance is provisioned, if not - delegate to the version using rel.rel() otherwise throw an exception about already being defined.

dmolesUC commented 6 years ago

It's an interesting question given that ResourceRepresentation.rels is a Vavr immutable Map and you're returning a new ResourceRepresentation object each time. I can see a scenario where one might want to take an existing object as a template and then intentionally override some small part of it; but I don't think it would happen that often in practice—accidental overrides seem much more likely. In which case IllegalArgumentException seems appropriate.