# API Validation

[Entity.validate()](https://dataclient.io/rest/api/Entity.md#validate) is called during normalization and denormalization.
`undefined` indicates no error, and a string error message if there is an error.

## Field check

Validation happens after [Entity.process()](https://dataclient.io/rest/api/Entity.md#process) but before [Entity.fromJS()](https://dataclient.io/rest/api/Entity.md#fromJS),
thus operates on POJOs rather than an instance of the class.

Here we can make sure the title field is included, and of the expected type.

```typescript title="api/Article"
import { Entity, RestEndpoint } from '@data-client/rest';

export class Article extends Entity {
  id = '';
  title = '';

  static validate(processedEntity) {
    if (!Object.hasOwn(processedEntity, 'title')) return 'missing title field';
    if (typeof processedEntity.title !== 'string') return 'title is wrong type';
  }
}

export const getArticle = new RestEndpoint({
  path: '/article/:id',
  schema: Article,
});
```

```html title="ArticlePage.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { getArticle } from './api/Article';

  const article = await useSuspense(getArticle, { id: '2' });
</script>

<template>
  <div>{{ article.title }}</div>
</template>
```

### All fields check

[validateRequired()](https://dataclient.io/rest/api/validateRequired.md) can be used to check if all defined fields are present.

```tsx title="api/Article"
import { Entity, RestEndpoint, validateRequired } from '@data-client/rest';

export class Article extends Entity {
  id = '';
  title = '';

  static validate(processedEntity) {
    return validateRequired(processedEntity, this.defaults);
  }
}

export const getArticle = new RestEndpoint({
  path: '/article/:id',
  schema: Article,
});
```

```html title="ArticlePage.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { getArticle } from './api/Article';

  const article = await useSuspense(getArticle, { id: '2' });
</script>

<template>
  <div>{{ article.title }}</div>
</template>
```

## Partial results

Another great use of validation is mixing endpoints that return [incomplete objects](https://dataclient.io/rest/guides/partial-entities.md). This is often
useful when some fields consume lots of bandwidth or are computationally expensive for the backend.

Consider using [validateRequired](https://dataclient.io/rest/api/validateRequired.md) to reduce code.

```typescript title="api/Article"
import { Entity, RestEndpoint } from '@data-client/rest';
import { Temporal } from 'temporal-polyfill';

export class ArticlePreview extends Entity {
  id = '';
  title = '';

  static key = 'Article';
}
export const getArticleList = new RestEndpoint({
  path: '/article',
  schema: [ArticlePreview],
});

export class ArticleFull extends ArticlePreview {
  content = '';
  createdAt = Temporal.Instant.fromEpochMilliseconds(0);

  static schema = {
    createdAt: Temporal.Instant.from,
  };

  static validate(processedEntity) {
    if (!Object.hasOwn(processedEntity, 'content')) return 'Missing content';
  }
}

export const getArticle = new RestEndpoint({
  path: '/article/:id',
  schema: ArticleFull,
});
```

```html title="ArticleDetail.vue"
<script setup lang="ts">
  import { useSuspense } from '@data-client/vue';
  import { getArticle } from './api/Article';

  const props = defineProps<{ id: string }>();
  const emit = defineEmits<{ home: [] }>();
  const article = await useSuspense(getArticle, () => ({ id: props.id }));
</script>

<template>
  <div>
    <h4>
      <a @click="emit('home')" style="cursor: pointer">&lt;</a>
      {{ article.title }}
    </h4>
    <div>
      <p>{{ article.content }}</p>
      <div>
        Created:
        <time>
          {{ article.createdAt.toLocaleString('en-US', { dateStyle: 'medium' }) }}
        </time>
      </div>
    </div>
  </div>
</template>
```

```html title="ArticleList.vue"
<script setup lang="ts">
  import { ref } from 'vue';
  import { useSuspense } from '@data-client/vue';
  import { getArticleList } from './api/Article';
  import ArticleDetail from './ArticleDetail.vue';

  const route = ref('');
  const articles = await useSuspense(getArticleList);
</script>

<template>
  <Suspense v-if="route">
    <ArticleDetail :id="route" @home="route = ''" />
    <template #fallback><div>loading...</div></template>
  </Suspense>
  <div v-else>
    <div
      v-for="article in articles"
      :key="article.pk()"
      @click="route = article.id"
      style="cursor: pointer; text-decoration: underline"
    >
      Click me: {{ article.title }}
    </div>
  </div>
</template>
```
