hydraNormalizer.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import type {AnyJson, ApiResponse, HydraMetadata} from '~/types/data'
  2. import UrlUtils from '~/services/utils/urlUtils'
  3. import {METADATA_TYPE} from '~/types/enum/data'
  4. import models from "~/models/models";
  5. import ApiResource from "~/models/ApiResource";
  6. import * as _ from 'lodash-es'
  7. /**
  8. * Normalisation et dé-normalisation du format de données Hydra
  9. */
  10. class HydraNormalizer {
  11. static models = models
  12. /**
  13. * Normalize the given entity into a Hydra formatted content.
  14. * @param entity
  15. */
  16. public static normalizeEntity(entity: ApiResource): AnyJson {
  17. const iriEncodedFields = Object.getPrototypeOf(entity).constructor.getIriEncodedFields()
  18. for (const field in iriEncodedFields) {
  19. const value = entity[field]
  20. const targetEntity = iriEncodedFields[field].entity
  21. if (_.isArray(value)) {
  22. entity[field] = value.map((id: number) => {
  23. return UrlUtils.makeIRI(targetEntity, id)
  24. })
  25. } else {
  26. entity[field] = UrlUtils.makeIRI(targetEntity, value)
  27. }
  28. }
  29. return entity.$toJson()
  30. }
  31. /**
  32. * Parse une réponse Hydra et retourne un objet ApiResponse
  33. *
  34. * @param {AnyJson} data
  35. * @param model
  36. * @return {AnyJson} réponse parsée
  37. */
  38. public static denormalize(data: AnyJson, model?: typeof ApiResource): ApiResponse {
  39. return {
  40. data: HydraNormalizer.getData(data, model),
  41. metadata: HydraNormalizer.getMetadata(data)
  42. }
  43. }
  44. protected static getData(hydraData: AnyJson, model?: typeof ApiResource): AnyJson {
  45. if (hydraData['@type'] === 'hydra:Collection') {
  46. const members = hydraData['hydra:member']
  47. return members.map((item: AnyJson) => HydraNormalizer.denormalizeItem(item, model))
  48. } else {
  49. return HydraNormalizer.denormalizeItem(hydraData, model)
  50. }
  51. }
  52. /**
  53. * Génère les métadonnées d'un item ou d'une collection
  54. *
  55. * @param data
  56. * @protected
  57. */
  58. protected static getMetadata(data: AnyJson): HydraMetadata {
  59. if (data['@type'] !== 'hydra:Collection') {
  60. // A single item, no metadata
  61. return { type: METADATA_TYPE.ITEM }
  62. }
  63. const metadata: HydraMetadata = {
  64. totalItems: data['hydra:totalItems']
  65. }
  66. if (data['hydra:view']) {
  67. /**
  68. * Extract the page number from the IRIs in the hydra:view section
  69. */
  70. const extractPageNumber = (pos: string, default_: number | undefined=undefined): number | undefined => {
  71. const iri = data['hydra:view']['hydra:' + pos]
  72. if (!iri) {
  73. return default_
  74. }
  75. return UrlUtils.getParameter(
  76. data['hydra:view']['hydra:' + pos],
  77. 'page',
  78. default_
  79. ) as number | undefined
  80. }
  81. // TODO: utile d'ajouter la page en cours?
  82. metadata.firstPage = extractPageNumber('first', 1)
  83. metadata.lastPage = extractPageNumber('last', 1)
  84. metadata.nextPage = extractPageNumber('next')
  85. metadata.previousPage = extractPageNumber('previous')
  86. }
  87. metadata.type = METADATA_TYPE.COLLECTION
  88. return metadata
  89. }
  90. /**
  91. * Dénormalise un item d'une réponse hydra
  92. *
  93. * @param item
  94. * @param model
  95. * @protected
  96. */
  97. protected static denormalizeItem(item: AnyJson, model?: typeof ApiResource): AnyJson {
  98. if (model) {
  99. return HydraNormalizer.denormalizeEntity(model, item)
  100. }
  101. if (!item.hasOwnProperty('@id')) {
  102. // Not hydra formatted
  103. console.error('De-normalization error : the item is not hydra formatted', item)
  104. return item
  105. }
  106. if (item['@id'].match(/\/api\/enum\/\w+/)) {
  107. return HydraNormalizer.denormalizeEnum(item)
  108. }
  109. let entity = null
  110. // On essaie de déterminer la nature de l'objet à partir de son id
  111. try {
  112. const iri = HydraNormalizer.parseEntityIRI(item['@id'])
  113. entity = iri.entity
  114. } catch (e) {
  115. console.error('De-normalization error : could not parse the IRI', item)
  116. return item
  117. }
  118. if (entity && HydraNormalizer.models.hasOwnProperty(entity)) {
  119. model = HydraNormalizer.models[entity]
  120. return HydraNormalizer.denormalizeEntity(model, item)
  121. }
  122. throw Error('De-normalization error : Could not determine the type of the entity '
  123. + item['@id'] + ' (found: ' + entity + ')')
  124. }
  125. protected static denormalizeEntity(model: typeof ApiResource, item: AnyJson) {
  126. const instance = new model(item)
  127. const iriEncodedFields = model.getIriEncodedFields()
  128. for (const field in iriEncodedFields) {
  129. const value = instance[field]
  130. if (_.isEmpty(value)) {
  131. continue
  132. }
  133. const targetEntity = iriEncodedFields[field].entity
  134. if (_.isArray(value)) {
  135. instance[field] = value.map((iri: string) => {
  136. return HydraNormalizer.getIdFromEntityIri(iri, targetEntity)
  137. })
  138. } else {
  139. instance[field] = HydraNormalizer.getIdFromEntityIri(value, targetEntity)
  140. }
  141. }
  142. return instance
  143. }
  144. protected static denormalizeEnum(item: AnyJson): AnyJson {
  145. return item
  146. }
  147. /**
  148. * Parse the given IRI
  149. * @param iri
  150. * @protected
  151. */
  152. protected static parseEntityIRI(iri: string) {
  153. const rx = /\/api\/(\w+)\/(\d+)/
  154. const match = rx.exec(iri)
  155. if (!match) {
  156. throw Error('could not parse the IRI : ' + JSON.stringify(iri))
  157. }
  158. return {
  159. entity: match[1],
  160. id: parseInt(match[2])
  161. }
  162. }
  163. /**
  164. * Retrieve the entitie's id from the given IRI
  165. * Throws an error if the IRI does not match the expected entity
  166. *
  167. * @param iri
  168. * @param expectedEntity
  169. * @protected
  170. */
  171. protected static getIdFromEntityIri(iri: string, expectedEntity: string): number | string {
  172. const { entity, id } = HydraNormalizer.parseEntityIRI(iri)
  173. if (entity !== expectedEntity) {
  174. throw Error("IRI entity does not match the field's target entity (" + entity + ' != ' + expectedEntity + ")")
  175. }
  176. return id
  177. }
  178. }
  179. export default HydraNormalizer