Skip to content

Commit

Permalink
feat: add emote to loaded file (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
meelrossi committed Aug 25, 2023
1 parent 2e99398 commit 1e39395
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/files/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
export const WEARABLE_MANIFEST = 'wearable.json'
export const EMOTE_MANIFEST = 'emote.json'
export const SCENE_MANIFEST = 'scene.json'
export const BUILDER_MANIFEST = 'builder.json'
17 changes: 17 additions & 0 deletions src/files/files.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ export class InvalidWearableConfigFileError extends Error {
}
}

export class InvalidEmoteConfigFileError extends Error {
public getErrors():
| ErrorObject<string, Record<string, unknown>, unknown>[]
| null
| undefined {
return this.errors
}

constructor(
private errors?:
| ErrorObject<string, Record<string, unknown>, unknown>[]
| null
) {
super('The emote config file is invalid')
}
}

export class InvalidSceneConfigFileError extends Error {
public getErrors():
| ErrorObject<string, Record<string, unknown>, unknown>[]
Expand Down
80 changes: 79 additions & 1 deletion src/files/files.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
EmoteCategory,
EmotePlayMode,
RequiredPermission,
BodyShape as WearableBodyShape
} from '@dcl/schemas'
Expand All @@ -9,13 +11,15 @@ import {
WEARABLE_MANIFEST,
MAX_FILE_SIZE,
BUILDER_MANIFEST,
SCENE_MANIFEST
SCENE_MANIFEST,
EMOTE_MANIFEST
} from './constants'
import { loadFile } from './files'
import {
FileNotFoundError,
FileTooBigError,
InvalidBuilderConfigFileError,
InvalidEmoteConfigFileError,
InvalidSceneConfigFileError,
InvalidWearableConfigFileError,
MissingRequiredPropertiesError,
Expand Down Expand Up @@ -595,6 +599,80 @@ describe('when loading an item file', () => {
})
})

describe('and the zip has an emote config file', () => {
describe('and the emote config file has unsupported properties', () => {
beforeEach(async () => {
zipFile.file(EMOTE_MANIFEST, '{ "unsupportedProp": "something" }')
zipFileContent = await zipFile.generateAsync({
type: 'uint8array'
})
})

it('should throw an error signaling that the wearable config file is invalid', () => {
return expect(loadFile(fileName, zipFileContent)).rejects.toThrow(
new InvalidEmoteConfigFileError()
)
})
})

describe('and the emote config file contains all properties', () => {
const mainModel = 'test.gltf'
let modelContent: Uint8Array

beforeEach(async () => {
modelContent = new Uint8Array([0, 1, 2, 3])
zipFile.file(
EMOTE_MANIFEST,
'{"name": "test", "description": "test d", "rarity": "unique", "category": "fun", "play_mode": "simple" }'
)
zipFile.file(mainModel, modelContent)
zipFileContent = await zipFile.generateAsync({
type: 'uint8array'
})
})

it('should build the LoadedFile contents with the zipped files, the main model file path and the builder information', () => {
return expect(loadFile(fileName, zipFileContent)).resolves.toEqual({
content: {
[mainModel]: modelContent,
[THUMBNAIL_PATH]: thumbnailContent
},
emote: {
name: 'test',
description: 'test d',
rarity: Rarity.UNIQUE,
category: EmoteCategory.FUN,
play_mode: EmotePlayMode.SIMPLE
},
mainModel
})
})
})

describe('and the emote config file contains a really long description', () => {
const mainModel = 'test.gltf'
let modelContent: Uint8Array

beforeEach(async () => {
modelContent = new Uint8Array([0, 1, 2, 3])
zipFile.file(
EMOTE_MANIFEST,
'{ "description": "this is a really loooooooooooooooooooooooooooooooooooong description" }'
)
zipFile.file(mainModel, modelContent)
zipFileContent = await zipFile.generateAsync({
type: 'uint8array'
})
})

it('should fail to build the loaded file', () => {
return expect(loadFile(fileName, zipFileContent)).rejects.toThrow(
new InvalidEmoteConfigFileError()
)
})
})
})

describe('and the zip file contains a file that exceeds the maximum file size limit', () => {
const modelFile = 'test.glb'

Expand Down
41 changes: 37 additions & 4 deletions src/files/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,27 @@ import {
WEARABLE_MANIFEST,
MAX_FILE_SIZE,
BUILDER_MANIFEST,
SCENE_MANIFEST
SCENE_MANIFEST,
EMOTE_MANIFEST
} from './constants'
import { WearableConfig, BuilderConfig, LoadedFile, SceneConfig } from './types'
import {
WearableConfig,
BuilderConfig,
LoadedFile,
SceneConfig,
EmoteConfig
} from './types'
import {
BuilderConfigSchema,
WearableConfigSchema,
SceneConfigSchema
SceneConfigSchema,
EmoteConfigSchema
} from './schemas'
import {
FileNotFoundError,
FileTooBigError,
InvalidBuilderConfigFileError,
InvalidEmoteConfigFileError,
InvalidWearableConfigFileError,
ModelFileNotFoundError,
WrongExtensionError
Expand All @@ -39,6 +48,7 @@ const validator = ajv
.addSchema(WearableConfigSchema, 'WearableConfig')
.addSchema(SceneConfigSchema, 'SceneConfig')
.addSchema(BuilderConfigSchema, 'BuilderConfig')
.addSchema(EmoteConfigSchema, 'EmoteConfig')

export async function loadFile<T extends Content>(
fileName: string,
Expand Down Expand Up @@ -110,7 +120,8 @@ async function handleZippedModelFiles<T extends Content>(
!basename(filePath).startsWith('.') &&
basename(filePath) !== WEARABLE_MANIFEST &&
basename(filePath) !== BUILDER_MANIFEST &&
basename(filePath) !== SCENE_MANIFEST
basename(filePath) !== SCENE_MANIFEST &&
basename(filePath) !== EMOTE_MANIFEST
) {
fileNames.push(filePath)
promiseOfFileContents.push(file.async(fileFormat) as Promise<T>)
Expand Down Expand Up @@ -138,10 +149,12 @@ async function handleZippedModelFiles<T extends Content>(
let wearable: WearableConfig | undefined = undefined
let scene: SceneConfig | undefined = undefined
let builder: BuilderConfig | undefined = undefined
let emote: EmoteConfig | undefined = undefined

const wearableZipFile = zip.file(WEARABLE_MANIFEST)
const sceneZipFile = zip.file(SCENE_MANIFEST)
const builderZipFile = zip.file(BUILDER_MANIFEST)
const emoteZipFile = zip.file(EMOTE_MANIFEST)

if (!wearableZipFile && sceneZipFile) {
throw new FileNotFoundError(WEARABLE_MANIFEST)
Expand Down Expand Up @@ -172,12 +185,21 @@ async function handleZippedModelFiles<T extends Content>(
builder = await loadBuilderConfig(builderZipFileContents)
}

if (emoteZipFile) {
const emoteZipFileContents = await emoteZipFile.async('uint8array')
emote = await loadEmoteConfig(emoteZipFileContents)
}

let result: LoadedFile<T> = { content }

if (builder) {
result = { ...result, builder }
}

if (emote) {
result = { ...result, emote }
}

if (wearable) {
result = { ...result, wearable, scene }
} else {
Expand Down Expand Up @@ -239,3 +261,14 @@ async function loadSceneConfig<T extends Content>(
}
return parsedContent
}

async function loadEmoteConfig<T extends Content>(
file: T
): Promise<EmoteConfig> {
const content = await readContent(file)
const parsedContent = JSON.parse(content)
if (!validator.validate('EmoteConfig', parsedContent)) {
throw new InvalidEmoteConfigFileError(validator.errors)
}
return parsedContent
}
41 changes: 39 additions & 2 deletions src/files/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import {
EmoteCategory,
EmotePlayMode,
HideableWearableCategory,
JSONSchema,
Scene,
WearableRepresentation
} from '@dcl/schemas'
import { WearableCategory, Rarity } from '../item/types'
import { BuilderConfig, SceneConfig, WearableConfig } from './types'
import {
BuilderConfig,
EmoteConfig,
SceneConfig,
WearableConfig
} from './types'

export const BuilderConfigSchema: JSONSchema<BuilderConfig> = {
type: 'object',
Expand All @@ -32,7 +39,8 @@ export const WearableConfigSchema: JSONSchema<WearableConfig> = {
},
description: {
type: 'string',
nullable: true
nullable: true,
maxLength: 64
},
rarity: {
...Rarity.schema,
Expand Down Expand Up @@ -79,3 +87,32 @@ export const WearableConfigSchema: JSONSchema<WearableConfig> = {
}

export const SceneConfigSchema: JSONSchema<SceneConfig> = Scene.schema

export const EmoteConfigSchema: JSONSchema<EmoteConfig> = {
type: 'object',
properties: {
name: {
type: 'string',
nullable: true
},
description: {
type: 'string',
nullable: true,
maxLength: 64
},
rarity: {
...Rarity.schema,
nullable: true
},
category: {
...EmoteCategory.schema,
nullable: true
},
play_mode: {
...EmotePlayMode.schema,
nullable: true
}
},
additionalProperties: false,
required: []
}
19 changes: 18 additions & 1 deletion src/files/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ErrorObject } from 'ajv/dist/core'
import { StandardProps, ThirdPartyProps, Wearable, Scene } from '@dcl/schemas'
import {
StandardProps,
ThirdPartyProps,
Wearable,
Scene,
EmoteCategory,
EmotePlayMode,
Rarity
} from '@dcl/schemas'
import { Content, RawContent } from '../content/types'

export type WearableConfig = Omit<
Expand All @@ -24,13 +32,22 @@ export type BuilderConfig = {
collectionId?: string
}

export type EmoteConfig = {
name?: string
description?: string
category?: EmoteCategory
rarity?: Rarity
play_mode?: EmotePlayMode
}

export type SceneConfig = Scene

export type LoadedFile<T extends Content> = {
content: RawContent<T>
wearable?: WearableConfig
scene?: SceneConfig
builder?: BuilderConfig
emote?: EmoteConfig
mainModel?: string
}

Expand Down

0 comments on commit 1e39395

Please sign in to comment.