Skip to content

Commit

Permalink
feat: context available to initialValue function and added schema to …
Browse files Browse the repository at this point in the history
…validation context

Initial value context contains client, schema, currentUser, projectId and dataset.
Validation context now also contains schema (alongside client and others).
  • Loading branch information
snorrees authored and rexxars committed Sep 14, 2022
1 parent 506579a commit deb115d
Show file tree
Hide file tree
Showing 27 changed files with 492 additions and 116 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ tsconfig.tsbuildinfo

# Sanity temp folders
.sanity

#IntelliJ
.idea
*.iml
4 changes: 1 addition & 3 deletions dev/test-studio/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import {createConfig, createPlugin} from 'sanity'
import {deskTool} from 'sanity/desk'
import {imageAssetSource} from './assetSources'
import {Branding} from './components/Branding'
import {CustomMarkers} from './components/formBuilder/CustomMarkers'
import {Markers} from './components/formBuilder/Markers'
import {resolveDocumentActions as documentActions} from './documentActions'
import {resolveInitialValueTemplates} from './initialValueTemplates'
import {languageFilter} from './plugins/language-filter'
import {schemaTypes} from './schema'
import {defaultDocumentNode, structure, newDocumentOptions} from './structure'
import {defaultDocumentNode, newDocumentOptions, structure} from './structure'
import {workshopTool} from './workshop'

const sharedSettings = createPlugin({
Expand Down
144 changes: 144 additions & 0 deletions dev/test-studio/schema/docs/v3/async-functions/schemaType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {ConfigContext, defineField, defineType} from 'sanity'
import {structureGroupOptions} from '../../../../structure/groupByOption'

export const validationTest = defineType({
type: 'document',
name: 'v3-validation',
title: 'v3 validation',
options: structureGroupOptions({
structureGroup: 'v3',
}),
fields: [
defineField({
type: 'string',
name: 'title',
title: 'Title',
validation: (Rule) =>
Rule.custom(
async (value, context) =>
new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line no-console
console.log('Context in custom validation function', context)
resolve(
`Always async error for. From context client->projectId: ${
context.client.config().projectId
}`
)
}, 1000)
})
),
initialValue: async (params: undefined, context: ConfigContext) => {
return new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line no-console
console.log('Context in delayed initial value function, string', context)
resolve(context.projectId)
}, 1000)
})
},
}),
{
type: 'array',
name: 'testArray',
title: 'Test array',
of: [
{
type: 'object',
fields: [
{
type: 'string',
name: 'title',
title: 'Title',
},
],
initialValue: async (params: undefined, context: ConfigContext) => {
return new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line no-console
console.log('Context in delayed initial value function, array object', context)
resolve({
title: context.projectId,
})
}, 1000)
})
},
},
],
initialValue: async (params: undefined, context: ConfigContext) => {
return new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line no-console
console.log('Context in delayed initial value function, array', context)
resolve([
{
_key: `${Math.random()}`,
title: context.projectId,
},
])
}, 1000)
})
},
},
{
title: 'Block',
name: 'blockText',
type: 'array',
of: [
{type: 'block'},
{
type: 'object',
name: 'testObject',
fields: [
{
type: 'string',
name: 'title',
title: 'Title',
},
],
initialValue: async (params: undefined, context: ConfigContext) => {
return new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line no-console
console.log(
'Context in delayed initial value function, block array object',
context
)
resolve({
_type: 'testObject',
title: context.projectId,
})
}, 1000)
})
},
},
],
initialValue: async (params: undefined, context: ConfigContext) => {
return new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line no-console
console.log('Context in delayed initial value function, block array', context)
resolve([
{
style: 'normal',
_type: 'block',
markDefs: [],
children: [
{
_type: 'span',
text: context.projectId,
marks: [],
},
],
},
{
_type: 'testObject',
title: context.projectId,
},
])
}, 1000)
})
},
},
],
})
5 changes: 4 additions & 1 deletion dev/test-studio/schema/docs/v3/example1/schemaType.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {structureGroupOptions} from '../../../../structure/groupByOption'
import {CodeInput} from './CodeInput'

export const example1SchemaType = {
type: 'document',
name: 'v3-example1',
title: 'v3 example #1',

options: structureGroupOptions({
structureGroup: 'v3',
}),
fields: [
{
type: 'string',
Expand Down
3 changes: 2 additions & 1 deletion dev/test-studio/schema/docs/v3/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {example1SchemaType} from './example1'
import {validationTest} from './async-functions/schemaType'

export const v3docs = {
types: [example1SchemaType],
types: [example1SchemaType, validationTest],
}
30 changes: 30 additions & 0 deletions dev/test-studio/structure/groupByOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type {StructureBuilder} from 'sanity/desk'
import {Schema} from '@sanity/types'

type StructureGroup = 'v3' // extend with union strings

export interface StructureGroupOption {
structureGroup?: StructureGroup
}

type MaybeStructureOptions = StructureGroupOption | undefined

export function typesInOptionGroup(
S: StructureBuilder,
schema: Schema,
groupName: StructureGroup
): string[] {
return S.documentTypeListItems()
.map((item) => item.getId())
.filter((id): id is string => {
return (
!!id && (schema.get(id)?.options as MaybeStructureOptions)?.structureGroup === groupName
)
})
}

export function structureGroupOptions<
O extends Required<StructureGroupOption> & Schema.ObjectOptions
>(options: O): O & Schema.ObjectOptions {
return options
}
12 changes: 11 additions & 1 deletion dev/test-studio/structure/resolveStructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
UsersIcon,
} from '@sanity/icons'
import {uuid} from '@sanity/uuid'
import {StructureResolver} from 'sanity/desk'
import {StructureBuilder, StructureResolver} from 'sanity/desk'
import {DebugPane} from '../components/panes/debug'
import {JsonDocumentDump} from '../components/panes/JsonDocumentDump'
import {_buildTypeGroup} from './_buildTypeGroup'
Expand All @@ -22,6 +22,8 @@ import {
STANDARD_PORTABLE_TEXT_INPUT_TYPES,
} from './constants'
import {delayValue} from './_helpers'
import {Schema} from '@sanity/types'
import {typesInOptionGroup} from './groupByOption'

export const structure: StructureResolver = (S, {schema}) => {
return S.list()
Expand Down Expand Up @@ -336,6 +338,14 @@ export const structure: StructureResolver = (S, {schema}) => {

S.divider(),

_buildTypeGroup(S, schema, {
id: 'v3',
title: 'V3 APIs',
types: typesInOptionGroup(S, schema, 'v3'),
}),

S.divider(),

...S.documentTypeListItems().filter((listItem) => {
const id = listItem.getId()

Expand Down
16 changes: 14 additions & 2 deletions packages/@sanity/types/src/schema/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Note: INCOMPLETE, but it's a start
import type {ComponentType} from 'react'
import {SanityClient} from '@sanity/client'
import type {Rule} from '../validation'
import type {ReferenceOptions} from '../reference'
import type {AssetSource} from '../assets'
Expand Down Expand Up @@ -123,7 +124,7 @@ export namespace Schema {
preview?: React.ComponentType<any> // @todo: use `PreviewProps` here
}
// TODO
initialValue?: any
initialValue?: InitialValueProperty<any, any>
}

export type TypeOptions<T extends Type> = T extends 'array'
Expand Down Expand Up @@ -450,7 +451,18 @@ export interface ConditionalPropertyCallbackContext {
export type ConditionalPropertyCallback = (context: ConditionalPropertyCallbackContext) => boolean
export type ConditionalProperty = boolean | ConditionalPropertyCallback | undefined

export type InitialValueResolver<Params, Value> = (params?: Params) => Promise<Value> | Value
export interface InitialValueResolverContext {
projectId: string
dataset: string
schema: Schema
currentUser: CurrentUser | null
client: SanityClient
}

export type InitialValueResolver<Params, Value> = (
params: Params | undefined,
context: InitialValueResolverContext
) => Promise<Value> | Value
export type InitialValueProperty<Params, Value> =
| Value
| InitialValueResolver<Params, Value>
Expand Down
2 changes: 2 additions & 0 deletions packages/@sanity/types/src/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {SchemaType, SchemaValidationValue} from '../schema'
import type {SanityDocument} from '../documents'
import type {ValidationMarker} from '../markers'
import type {Block} from '../portableText'
import {Schema} from '../schema'

export type RuleTypeConstraint = 'Array' | 'Boolean' | 'Date' | 'Number' | 'Object' | 'String'
export type FieldRules = {[fieldKey: string]: SchemaValidationValue}
Expand Down Expand Up @@ -215,6 +216,7 @@ export type RuleSpecConstraint<T extends RuleSpec['flag']> = ConditionalIndexAcc
*/
export type ValidationContext = {
client: SanityClient
schema: Schema
parent?: unknown
type?: SchemaType
document?: SanityDocument
Expand Down
6 changes: 6 additions & 0 deletions packages/@sanity/validation/src/validateDocument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ describe('validateItem', () => {
await expect(
validateItem({
client: client as any,
schema,
value: {},
document: undefined,
path: [],
Expand Down Expand Up @@ -237,6 +238,7 @@ describe('validateItem', () => {
await expect(
validateItem({
client: client as any,
schema,
document: undefined,
parent: undefined,
path: undefined,
Expand Down Expand Up @@ -403,6 +405,7 @@ describe('validateItem', () => {

const result = await validateItem({
client: client as any,
schema,
document: document,
parent: undefined,
path: [],
Expand Down Expand Up @@ -505,6 +508,7 @@ describe('validateItem', () => {
await expect(
validateItem({
client: client as any,
schema,
document: undefined,
parent: undefined,
path: [],
Expand Down Expand Up @@ -603,6 +607,7 @@ describe('validateItem', () => {
await expect(
validateItem({
client: client as any,
schema,
value,
type: rootType,
document,
Expand Down Expand Up @@ -745,6 +750,7 @@ describe('validateItem', () => {

const resultPromise = validateItem({
client: client as any,
schema,
value,
document: undefined,
parent: undefined,
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/validation/src/validateDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default async function validateDocument(
try {
return await validateItem({
client,
schema,
parent: undefined,
value: doc,
path: [],
Expand Down
8 changes: 6 additions & 2 deletions packages/@sanity/validation/test/infer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ describe('schema validation inference', () => {

async function expectNoError(validations: Rule[], value: unknown) {
const errors = (
await Promise.all(validations.map((rule) => rule.validate(value, {client: {} as any})))
await Promise.all(
validations.map((rule) => rule.validate(value, {client: {} as any, schema: {} as any}))
)
).flat()
if (errors.length === 0) {
// This shouldn't actually be needed, but counts against an assertion in jest-terms
Expand All @@ -323,7 +325,9 @@ async function expectError(
level = 'error'
) {
const errors = (
await Promise.all(validations.map((rule) => rule.validate(value, {client: {} as any})))
await Promise.all(
validations.map((rule) => rule.validate(value, {client: {} as any, schema: {} as any}))
)
).flat()
if (!errors.length) {
throw new Error(`Expected error matching "${message}", but no errors were returned.`)
Expand Down
Loading

0 comments on commit deb115d

Please sign in to comment.