Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add Icon schema #152

Merged
merged 6 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is horrible but you can't use numbers as elements of an enum

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha! I guess so, maybe align the field numbers for sanity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enums must start at 0 :'|

Copy link
Member

@achou11 achou11 Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it work if you do something like 1x, 2x (or maybe x1, x2)? not sure if any better but just an idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the second option works. I'm adding it since its less confusing to look at...

one = 0;
two = 1;
three = 2;
tomasciccola marked this conversation as resolved.
Show resolved Hide resolved
}

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 {
tomasciccola marked this conversation as resolved.
Show resolved Hide resolved
switch (pixelDensity) {
case 'one':
return 1
case 'two':
return 2
case 'three':
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 'one'
case 2:
return 'two'
case 3:
return 'three'
}
}

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: {},
},
]
Loading