Skip to content

Commit 24a99d2

Browse files
committed
#1445, #1443, #1439, #1438, #1427 - Documentation for HAL-FORMS.
1 parent 2a4c5ca commit 24a99d2

File tree

2 files changed

+128
-22
lines changed

2 files changed

+128
-22
lines changed

src/main/asciidoc/mediatypes.adoc

+127-21
Original file line numberDiff line numberDiff line change
@@ -320,12 +320,35 @@ identically to <<mediatypes.hal,HAL documents>>.
320320
HAL-FORMS allows to describe criterias for each form field.
321321
Spring HATEOAS allows to customize those by shaping the model type for the input and output types and using annotations on them.
322322

323+
Each template will get the following attributes defined:
324+
325+
.Template attributes
326+
[options="header", cols="1,4"]
327+
|===============
328+
|Attribute|Description
329+
|`contentType`| The media type expected to be received by the server. Only included if the controller method pointed to exposes a `@RequestMapping(consumes = "…")` attribute, or the media type was defined explicitly when setting up the affordance.
330+
|`method`| The HTTP method to use when submitting the template.
331+
|`target`| The target URI to submit the form to. Will only be rendered if the affordance target is different than the link it was declared on.
332+
|`title`| The human readable title when displaying the template.
333+
|`properties`| All properties to be submitted with the form (see below).
334+
|===============
335+
336+
Each property will get the following attributes defined:
337+
338+
.Property attributes
323339
[options="header", cols="1,4"]
324340
|===============
325341
|Attribute|Description
326342
|`readOnly`| Set to `true` if there's no setter method for the property. If that is present, use Jackson's `@JsonProperty(Access.READ_ONLY)` on the accessors or field explicitly. Not rendered by default, thus defaulting to `false`.
327343
|`regex`| Can be customized by using JSR-303's `@Pattern` annotation either on the field or a type. In case of the latter the pattern will be used for every property declared as that particular type. Not rendered by default.
328344
|`required`| Can be customized by using JSR-303's `@NotNull`. Not rendered by default and thus defaulting to `false`. Templates using `PATCH` as method will automatically have all properties set to not required.
345+
|`max`| The maximum value allowed for the property. Derived from Hibernate Validator's `@Range` or JSR-303's `@Max` annotations.
346+
|`maxLength`| The maximum length value allowed for the property. Derived from Hibernate Validator's `@Length` annotation.
347+
|`min`| The minimum value allowed for the property. Derived from Hibernate Validator's `@Range` or JSR-303's `@Min` annotations.
348+
|`minLength`| The minimum length value allowed for the property. Derived from Hibernate Validator's `@Length` annotation.
349+
|`prompt`| The user readable prompt to use when rendering the form input. For details, see <<mediatypes.hal-forms.i18n.prompts>>.
350+
|`placeholder`| A user readable placeholder, to give an example for a format expected. The way of defining those follows <<mediatypes.hal-forms.i18n.prompts>> but uses the suffix `_placeholder`.
351+
|`type`| The HTML input type derived from the explicit `@InputType` annotation, JSR-303 validation annotations or the property's type.
329352
|===============
330353

331354
For types that you cannot annotate manually, you can register a custom pattern via a `HalFormsConfiguration` bean present in the application context.
@@ -351,6 +374,7 @@ This setup will cause the HAL-FORMS template properties for representation model
351374
HAL-FORMS contains attributes that are intended for human interpretation, like a template's title or property prompts.
352375
These can be defined and internationalized using Spring's resource bundle support and the `rest-messages` resource bundle configured by Spring HATEOAS by default.
353376

377+
[[mediatypes.hal-forms.i18n.template-titles]]
354378
==== Template titles
355379
To define a template title use the following pattern: `_templates.$affordanceName.title`. Note that in HAL-FORMS, the name of a template is `default` if it is the only one.
356380
This means that you'll usually have to qualify the key with the local or fully qualified input type name that affordance describes.
@@ -365,13 +389,14 @@ Employee._templates.default.title=Create employee <3>
365389
com.acme.Employee._templates.default.title=Create employee <4>
366390
----
367391
<1> A global definition for the title using `default` as key.
368-
<2> A global definition for the title using the actual affordance name as key. Unless defined explicitly when creating the affordance, this defaults to `$httpMethod + $simpleInputTypeName`.
392+
<2> A global definition for the title using the actual affordance name as key. Unless defined explicitly when creating the affordance, this defaults to the name of the method that has been pointed to when creating the affordance.
369393
<3> A locally defined title to be applied to all types named `Employee`.
370394
<4> A title definition using the fully-qualified type name.
371395
====
372396

373397
NOTE: Keys using the actual affordance name enjoy preference over the defaulted ones.
374398

399+
[[mediatypes.hal-forms.i18n.prompts]]
375400
==== Property prompts
376401
Property prompts can also be resolved via the `rest-messages` resource bundle automatically configured by Spring HATEOAS.
377402
The keys can be defined globally, locally or fully-qualified and need an `._prompt` concatenated to the actual property key:
@@ -389,37 +414,118 @@ com.acme.Employee.firstName._prompt=Firstname <3>
389414
<3> The `firstName` property of `com.acme.Employee` will get a prompt of "Firstname" assigned.
390415
====
391416

392-
A sample document with both template titles and property prompts defined would then look something like this:
417+
[[mediatypes.hal-forms.example]]
418+
=== A complete example
393419

394-
.A sample HAL-FORMS document with internationalized template titles and property prompts
395-
====
420+
Let's have a look at some example code that combines all the definition and customization attributes described above.
421+
A `RepresentationModel` for a customer might look something like this:
422+
423+
[source, java]
424+
----
425+
class CustomerRepresentation
426+
extends RepresentationModel<CustomerRepresentation> {
427+
428+
String name;
429+
LocalDate birthdate; <1>
430+
@Pattern(regex = "[0-9]{16}") String ccn; <2>
431+
@Email String email; <3>
432+
}
433+
----
434+
<1> We define a `birthdate` property of type `LocalDate`.
435+
<2> We expect `ccn` to adhere to a regular expression.
436+
<3> We define `email` to be an email using the JSR-303 `@Email` annotation.
437+
438+
Note that this type is not a domain type.
439+
It's intentionally designed to capture a wide range of potentially invalid input so that potentialy erroneous valies for the fields can be rejected at once.
440+
441+
Let's continue by having a look at how a controller makes use of that model:
442+
443+
[source, java]
444+
----
445+
@Controller
446+
class CustomerController {
447+
448+
@PostMapping("/customers")
449+
EntityModel<?> createCustomer(@RequestBody CustomerRepresentation payload) { <1>
450+
// …
451+
}
452+
453+
@GetMapping("/customers")
454+
CollectionModel<?> getCustomers() {
455+
456+
CollectionModel<?> model = …;
457+
458+
CustomerController controller = methodOn(CustomerController.class);
459+
460+
model.add(linkTo(controller.getCustomers()).withSelfRel() <2>
461+
.andAfford(controller.createCustomer(null)));
462+
463+
return ResponseEntity.ok(model);
464+
}
465+
}
466+
----
467+
<1> A controller method is declared to use the representation model defined above to bind the request body to if a `POST` is issued to `/customers`.
468+
<2> A `GET` request to `/customers` prepares a model, adds a `self` link to it and additionally declares an affordance on that very link pointing to the controller method mapped to `POST`.
469+
This will cause an <<server.affordances, affordance model>> to be built up, which -- depending on the media type to be rendered eventually -- will be translated into the media type specific format.
470+
471+
Next, let's add some additional metadata to make the form more accessible to humans:
472+
473+
.Additional properties declared in `rest-messages.properties`.
396474
[source]
397475
----
476+
CustomerRepresentation._template.createCustomer.title=Create customer <1>
477+
CustomerRepresentation.ccn._prompt=Credit card number <2>
478+
CustomerRepresentation.ccn._placeholder=1234123412341234 <2>
479+
----
480+
<1> We define an explicit title for the template created by pointing to the `createCustomer(…)` method.
481+
<2> We explicitly a prompt and placeholder for the `ccn` property of the `CustomerRepresentation` model.
482+
483+
If a client now issues a `GET` request to `/customers` using an `Accept` header of `application/prs.hal-forms+json`, the response HAL document is extended to a HAL-FORMS one to include the following `_templates` definition:
484+
485+
[source, json]
486+
----
398487
{
399488
…,
400489
"_templates" : {
401-
"default" : {
402-
"title" : "Create employee",
403-
"method" : "put",
404-
"contentType" : "",
490+
"default" : { <1>
491+
"title" : "Create customer", <2>
492+
"method" : "post", <3>
405493
"properties" : [ {
406-
"name" : "firstName",
407-
"prompt" : "Firstname",
408-
"required" : true
409-
}, {
410-
"name" : "lastName",
411-
"prompt" : "Lastname",
412-
"required" : true
413-
}, {
414-
"name" : "role",
415-
"prompt" : "Role",
416-
"required" : true
417-
} ]
494+
"name" : "name",
495+
"required" : true,
496+
"type" : "text" <4>
497+
} , {
498+
"name" : "birthdate",
499+
"required" : true,
500+
"type" : "date" <4>
501+
} , {
502+
"name" : "ccn",
503+
"prompt" : "Credit card number", <5>
504+
"placeholder" : "1234123412341234" <5>
505+
"required" : true,
506+
"regex" : "[0-9]{16}", <6>
507+
"type" : "text"
508+
} , {
509+
"name" : "email",
510+
"prompt" : "Email",
511+
"required" : true,
512+
"type" : "email" <7>
513+
} ]
418514
}
419515
}
420516
}
421517
----
422-
====
518+
<1> A template named `default` is exposed. Its name is `default` as it's the sole template defined and the spec requires that name to be used.
519+
If multiple templates are attached (by declaring additional affordances) they will be each named after the method they're pointing to.
520+
<2> The template title is derived from the value defined in the resource bundle. Note, that depending on the `Accept-Language` header sent with the request and the availability different values might returned.
521+
<3> The `method` attribute's value is derived from the mapping of the method the affordance was derived from.
522+
<4> The `type` attribute's value `text` is derived from the property's type `String`.
523+
The same applies to `birthdate` property, but resulting in `date`.
524+
<5> The prompt and placeholder for the `ccn` property are derived from the resource bundle as well.
525+
<6> The `@Pattern` declaration for the `ccn` property is exposed as `regex` attribute of the template property.
526+
<7> The `@Email` annotation on the `email` property has been translated into the corresponding `type` value.
527+
528+
HAL-FORMS templates are considered by e.g. the https://github.com/toedter/hal-explorer[HAL Explorer], which automatically renders HTML forms from those descriptions.
423529

424530
[[mediatypes.http-problem]]
425531
== HTTP Problem Details

src/main/asciidoc/server.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ return new ResponseEntity<PersonModel>(headers, HttpStatus.CREATED);
7272
====
7373

7474
[[fundamentals.obtaining-links.builder.methods]]
75-
==== Building links that point to methods
75+
=== Building links that point to methods
7676

7777
You can even build links that point to methods or create dummy controller method invocations.
7878
The first approach is to hand a `Method` instance to the `WebMvcLinkBuilder`.

0 commit comments

Comments
 (0)