Skip to content

Commit

Permalink
Feat: add Icon schema (#152)
Browse files Browse the repository at this point in the history
* add Icon {proto,schema} and {encode,decode}-conversions

* Update src/lib/decode-conversions.ts

Co-authored-by: Gregor MacLennan <[email protected]>

* Update proto/icon/v1.proto

Co-authored-by: Gregor MacLennan <[email protected]>

* reverted icon/v1.proto PixelDensity enum

in protobufs, enums need to start at 0, so we can't align the numbers...

* remove unnecessary type casting, add tests

* call pixelDensity x1,x2,x3 on proto

---------

Co-authored-by: Tomás Ciccola <[email protected]>
Co-authored-by: Gregor MacLennan <[email protected]>
  • Loading branch information
3 people authored Oct 3, 2023
1 parent 01db7b3 commit dacb41e
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 0 deletions.
42 changes: 42 additions & 0 deletions proto/icon/v1.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
syntax = "proto3";
package mapeo;

import "common/v1.proto";
import "options.proto";

message Icon_1 {
// **DO NOT CHANGE dataTypeId** generated with `openssl rand -hex 6`
option (dataTypeId) = "97e8cd9f0854";
option (schemaName) = "icon";

Common_1 common = 1;
string name = 2 [(required) = true];

message IconVariant {

enum Size {
small = 0;
medium = 1;
large = 2;
}

enum PixelDensity {
x1 = 0;
x2 = 1;
x3 = 2;
}

enum MimeType {
svg = 0;
png = 1;
}

Size size = 1 [(required) = true];
PixelDensity pixelDensity = 2 [(required) = true];
bytes blobVersionId = 3 [(required) = true];
MimeType mimeType = 4 [(required) = true];
}

repeated IconVariant variants = 3;

}
43 changes: 43 additions & 0 deletions schema/icon/v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://mapeo.world/schemas/icon/v2.json",
"title": "Icon",
"description": "An Icon represents metadata to retrieve an Icon blob",
"type": "object",
"properties": {
"schemaName": {
"description": "Must be `icon`",
"type": "string",
"const": "icon"
},
"name": {
"type": "string"
},
"variants" :{
"type": "array",
"items": {
"type": "object",
"properties": {
"size": {
"type": "string",
"enum": ["small", "medium", "large"]
},
"pixelDensity": {
"type": "number",
"enum": [1,2,3]
},
"blobVersionId": {
"type": "string"
},
"mimeType": {
"type": "string",
"enum": ["image/svg+xml", "image/png"]
}
},
"required": ["size", "pixelDensity", "blobVersionId", "mimeType"]
}
}
},
"required": ["schemaName", "name", "variants"],
"additionalProperties": false
}
3 changes: 3 additions & 0 deletions src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
convertRole,
convertDeviceInfo,
convertCoreOwnership,
convertIcon,
} from './lib/decode-conversions.js'
// @ts-ignore
import * as cenc from 'compact-encoding'
Expand Down Expand Up @@ -68,6 +69,8 @@ export function decode(
return convertDeviceInfo(message, versionObj)
case 'coreOwnership':
return convertCoreOwnership(message, versionObj)
case 'icon':
return convertIcon(message, versionObj)
default:
const _exhaustiveCheck: never = message
return message
Expand Down
6 changes: 6 additions & 0 deletions src/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
convertRole,
convertDeviceInfo,
convertCoreOwnership,
convertIcon,
} from './lib/encode-conversions.js'
import { CoreOwnership } from './index.js'

Expand Down Expand Up @@ -72,6 +73,11 @@ export function encode(
protobuf = Encode[mapeoDoc.schemaName](message).finish()
break
}
case 'icon': {
const message = convertIcon(mapeoDoc)
protobuf = Encode[mapeoDoc.schemaName](message).finish()
break
}
default:
const _exhaustiveCheck: never = mapeoDoc
protobuf = _exhaustiveCheck
Expand Down
56 changes: 56 additions & 0 deletions src/lib/decode-conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import {
type TagValue_1,
type TagValue_1_PrimitiveValue,
} from '../proto/tags/v1.js'

import {
type Icon_1_IconVariant,
Icon_1_IconVariant_MimeType,
Icon_1_IconVariant_PixelDensity,
} from '../proto/icon/v1.js'

import {
type MapeoDoc,
type ProtoTypesWithSchemaInfo,
Expand Down Expand Up @@ -187,6 +194,55 @@ export const convertCoreOwnership: ConvertFunction<'coreOwnership'> = (
}
}

export const convertIcon: ConvertFunction<'icon'> = (message, versionObj) => {
const { common, schemaVersion, ...rest } = message
const jsonSchemaCommon = convertCommon(common, versionObj)
return {
...jsonSchemaCommon,
...rest,
variants: message.variants.map((variant) => convertIconVariant(variant)),
}
}

function convertIconVariant(variant: Icon_1_IconVariant) {
const { blobVersionId, mimeType, size, pixelDensity } = variant
return {
blobVersionId: blobVersionId.toString('hex'),
mimeType: convertIconMimeType(mimeType),
size: size === 'UNRECOGNIZED' ? 'medium' : size,
pixelDensity: convertIconPixelDensity(pixelDensity),
}
}

function convertIconPixelDensity(
pixelDensity: Icon_1_IconVariant_PixelDensity
): 1 | 2 | 3 {
switch (pixelDensity) {
case 'x1':
return 1
case 'x2':
return 2
case 'x3':
return 3
default:
return 1
}
}

type ValidMimeTypes = 'image/svg+xml' | 'image/png'
function convertIconMimeType(
mimeType: Icon_1_IconVariant_MimeType
): ValidMimeTypes {
switch (mimeType) {
case 'svg':
return 'image/svg+xml'
case 'png':
return 'image/png'
default:
return 'image/svg+xml'
}
}

function convertTags(tags: { [key: string]: TagValue_1 } | undefined): {
[key: string]: Exclude<JsonTagValue, undefined>
} {
Expand Down
44 changes: 44 additions & 0 deletions src/lib/encode-conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
MapeoDocInternal,
} from '../types.js'
import { TagValue_1, type TagValue_1_PrimitiveValue } from '../proto/tags/v1.js'
import { Icon } from '../schema/icon.js'
import { type Icon_1_IconVariant } from '../proto/icon/v1.js'
import { Observation_5_Metadata } from '../proto/observation/v5.js'
import { parseVersionId } from './utils.js'
import { CoreOwnership } from '../index.js'
Expand Down Expand Up @@ -132,6 +134,48 @@ export const convertCoreOwnership = (
}
}

export const convertIcon: ConvertFunction<'icon'> = (mapeoDoc) => {
const { variants, ...rest } = mapeoDoc
return {
common: convertCommon(mapeoDoc),
...rest,
variants: convertIconVariants(variants),
}
}

function convertIconVariants(variants: Icon['variants']): Icon_1_IconVariant[] {
return variants.map((variant) => {
const { blobVersionId, mimeType, size, pixelDensity } = variant
return {
blobVersionId: Buffer.from(blobVersionId, 'hex'),
mimeType: convertIconMimeType(mimeType),
size,
pixelDensity: convertIconPixelDensity(pixelDensity),
}
})
}
function convertIconMimeType(mimeType: 'image/svg+xml' | 'image/png') {
switch (mimeType) {
case 'image/svg+xml':
return 'svg'
case 'image/png':
return 'png'
default:
return 'svg'
}
}

function convertIconPixelDensity(pixelDensity: 1 | 2 | 3) {
switch (pixelDensity) {
case 1:
return 'x1'
case 2:
return 'x2'
case 3:
return 'x3'
}
}

function convertCommon(
common: Omit<MapeoCommon, 'versionId'>
): ProtoTypesWithSchemaInfo['common'] {
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type SupportedSchemaNames =
| 'field'
| 'preset'
| 'role'
| 'icon'
| 'deviceInfo'
| 'coreOwnership'

Expand Down
21 changes: 21 additions & 0 deletions test/fixtures/bad-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,25 @@ export const badDocs = [
deleted: false,
},
},
{
text: 'icon without name',
doc: {
docId: cachedValues.docId,
versionId: cachedValues.versionId,
schemaName: 'icon',
createdAt: cachedValues.createdAt,
updatedAt: cachedValues.updatedAt,
createdBy: cachedValues.createdBy,
links: [],
deleted: false,
variants: [
{
size: 'large',
pixelDensity: 3,
mimeType: 'image/png',
blobVersionId: 'someRandomString',
},
],
},
},
]
29 changes: 29 additions & 0 deletions test/fixtures/good-docs-completed.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check

import { cachedValues } from './cached.js'
import { randomBytes } from 'node:crypto'

/**
* @type {Array<{
Expand Down Expand Up @@ -199,4 +200,32 @@ export const goodDocsCompleted = [
},
expected: {},
},
{
doc: {
docId: cachedValues.docId,
versionId: cachedValues.versionId,
schemaName: 'icon',
name: 'tree',
createdAt: cachedValues.createdAt,
updatedAt: cachedValues.updatedAt,
createdBy: cachedValues.createdBy,
links: [],
deleted: false,
variants: [
{
size: 'small',
pixelDensity: 1,
blobVersionId: randomBytes(32).toString('hex'),
mimeType: 'image/png',
},
{
size: 'large',
pixelDensity: 3,
blobVersionId: randomBytes(32).toString('hex'),
mimeType: 'image/svg+xml',
},
],
},
expected: {},
},
]
23 changes: 23 additions & 0 deletions test/fixtures/good-docs-minimal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
import { cachedValues } from './cached.js'
import { randomBytes } from 'node:crypto'

/**
* The `expected` is a partial doc of the extra fields that we expect to be set
Expand Down Expand Up @@ -132,4 +133,26 @@ export const goodDocsMinimal = [
},
expected: {},
},
{
doc: {
docId: cachedValues.docId,
versionId: cachedValues.versionId,
schemaName: 'icon',
name: 'tree',
createdAt: cachedValues.createdAt,
updatedAt: cachedValues.updatedAt,
createdBy: cachedValues.createdBy,
links: [],
deleted: false,
variants: [
{
size: 'small',
pixelDensity: 1,
blobVersionId: randomBytes(32).toString('hex'),
mimeType: 'image/png',
},
],
},
expected: {},
},
]

0 comments on commit dacb41e

Please sign in to comment.