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: two icon types (svg and png) #174

Merged
merged 6 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
33 changes: 18 additions & 15 deletions proto/icon/v1.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,39 @@ message Icon_1 {
Common_1 common = 1;
string name = 2 [(required) = true];

message IconVariant {

enum Size {
small = 0;
medium = 1;
large = 2;
}
message IconVariantSvg {}
tomasciccola marked this conversation as resolved.
Show resolved Hide resolved

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

enum MimeType {
svg = 0;
png = 1;
PixelDensity pixelDensity = 1 [(required) = true];
}


message IconVariant {
oneof variant {
IconVariantSvg svgIcon = 1;
IconVariantPng pngIcon = 2;
}

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

message BlobVersionId {
bytes coreDiscoveryKey = 1;
int32 index = 2;
}

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

repeated IconVariant variants = 3;

}
57 changes: 39 additions & 18 deletions schema/icon/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
"title": "Icon",
"description": "An Icon represents metadata to retrieve an Icon blob",
"type": "object",
"definitions": {
"size": {
"type": "string",
"enum": ["small", "medium", "large"]
},
"blobVersionId": {
"description": "Version id of the icon blob. Each id is id (hex-encoded 32 byte buffer) and index number, separated by '/'",
"type": "string"
}
},
"properties": {
"schemaName": {
"description": "Must be `icon`",
Expand All @@ -13,29 +23,40 @@
"name": {
"type": "string"
},
"variants" :{
"variants": {
"type": "array",
"items": {
"type": "object",
"properties": {
"size": {
"type": "string",
"enum": ["small", "medium", "large"]
},
"pixelDensity": {
"type": "number",
"enum": [1,2,3]
},
"blobVersionId": {
"description": "Version id of the icon blob. Each id is id (hex-encoded 32 byte buffer) and index number, separated by '/'",
"type": "string"
"oneOf": [
{
"type": "object",
"properties": {
"mimeType": {
"type": "string",
"const": "image/png"
},
"size": { "$ref": "#/definitions/size" },
"pixelDensity": {
"type": "number",
"enum": [1, 2, 3]
},
"blobVersionId": { "$ref": "#/definitions/blobVersionId" }
},
"required": ["size", "pixelDensity", "blobVersionId", "mimeType"]
},
"mimeType": {
"type": "string",
"enum": ["image/svg+xml", "image/png"]
{
"type": "object",
"properties": {
"size": { "$ref": "#/definitions/size" },
"mimeType": {
"type": "string",
"const": "image/svg+xml"
},
"blobVersionId": { "$ref": "#/definitions/blobVersionId" }
},
"required": ["size", "blobVersionId", "mimeType"]
}
},
"required": ["size", "pixelDensity", "blobVersionId", "mimeType"]
]
}
}
},
Expand Down
5 changes: 2 additions & 3 deletions src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
// @ts-ignore
import * as cenc from 'compact-encoding'
import { DATA_TYPE_ID_BYTES, SCHEMA_VERSION_BYTES } from './constants.js'
import { VersionIdObject, getProtoTypeName } from './lib/utils.js'
import { ExhaustivenessError, VersionIdObject, getProtoTypeName } from './lib/utils.js'

/** Map of dataTypeIds to schema names for quick lookups */
const dataTypeIdToSchemaName: Record<string, SchemaName> = {}
Expand Down Expand Up @@ -75,8 +75,7 @@ export function decode(
case 'translation':
return convertTranslation(message, versionObj)
default:
const _exhaustiveCheck: never = message
return message
throw new ExhaustivenessError(message)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
convertTranslation,
} from './lib/encode-conversions.js'
import { CoreOwnership } from './index.js'
import { ExhaustivenessError } from './lib/utils.js'

/**
* Encode a an object validated against a schema as a binary protobuf prefixed
Expand Down Expand Up @@ -85,8 +86,7 @@ export function encode(
break
}
default:
const _exhaustiveCheck: never = mapeoDoc
protobuf = _exhaustiveCheck
throw new ExhaustivenessError(mapeoDoc)
}

return Buffer.concat([blockPrefix, protobuf])
Expand Down
58 changes: 34 additions & 24 deletions src/lib/decode-conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {

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

import {
Expand All @@ -21,7 +20,7 @@ import {
type JsonTagValue,
type MapeoDocInternal,
} from '../types.js'
import { VersionIdObject, getVersionId } from './utils.js'
import { ExhaustivenessError, VersionIdObject, getVersionId } from './utils.js'

/** Function type for converting a protobuf type of any version for a particular
* schema name, and returning the most recent JSONSchema type */
Expand Down Expand Up @@ -218,20 +217,47 @@ export const convertTranslation: ConvertFunction<'translation'> = (
}

function convertIconVariant(variant: Icon_1_IconVariant) {
const { blobVersionId, mimeType, size, pixelDensity } = variant
if (variant.variant?.$case === 'pngIcon') {
const { pixelDensity } = variant.variant.pngIcon
return convertIconVariantPng({ ...variant, pixelDensity })
} else if (variant.variant?.$case === 'svgIcon') {
return convertIconVariantSvg(variant)
} else {
throw new Error('invalid icon variant type')
}
}

function convertIconVariantPng(
variant: Icon_1_IconVariant & {
pixelDensity: Icon_1_IconVariantPng_PixelDensity
}
) {
const { blobVersionId, size, pixelDensity } = variant
if (!blobVersionId) {
throw new Error('Missing required property `blobVersionId`')
}
return {
blobVersionId: getVersionId(blobVersionId),
mimeType: convertIconMimeType(mimeType),
mimeType: 'image/png' as const,
size: size === 'UNRECOGNIZED' ? 'medium' : size,
pixelDensity: convertIconPixelDensity(pixelDensity),
}
}

function convertIconVariantSvg(variant: Icon_1_IconVariant) {
const { blobVersionId, size } = variant
if (!blobVersionId) {
throw new Error('Missing required property `blobVersionId`')
}
EvanHahn marked this conversation as resolved.
Show resolved Hide resolved
return {
blobVersionId: getVersionId(blobVersionId),
mimeType: 'image/svg+xml' as const,
size: size === 'UNRECOGNIZED' ? 'medium' : size,
}
}

function convertIconPixelDensity(
pixelDensity: Icon_1_IconVariant_PixelDensity
pixelDensity: Icon_1_IconVariantPng_PixelDensity
): 1 | 2 | 3 {
switch (pixelDensity) {
case 'x1':
Expand All @@ -245,20 +271,6 @@ function convertIconPixelDensity(
}
}

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 Expand Up @@ -291,8 +303,7 @@ function convertTagValue({ kind }: TagValue_1): JsonTagValue {
case 'primitive_value':
return convertTagPrimitive(kind.primitive_value)
default:
const _exhaustiveCheck: never = kind
return kind
throw new ExhaustivenessError(kind)
}
}

Expand All @@ -310,8 +321,7 @@ function convertTagPrimitive({
case 'string_value':
return kind.string_value
default:
const _exhaustiveCheck: never = kind
return _exhaustiveCheck
throw new ExhaustivenessError(kind)
}
}

Expand Down
64 changes: 36 additions & 28 deletions src/lib/encode-conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ 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 { ExhaustivenessError, parseVersionId } from './utils.js'
import { CoreOwnership } from '../index.js'

/** Function type for converting a protobuf type of any version for a particular
Expand Down Expand Up @@ -143,37 +143,35 @@ export const convertIcon: ConvertFunction<'icon'> = (mapeoDoc) => {
}
}

export const convertTranslation: ConvertFunction<'translation'> = (
mapeoDoc
) => {
return {
common: convertCommon(mapeoDoc),
...mapeoDoc,
docIdRef: Buffer.from(mapeoDoc.docIdRef, 'hex'),
}
}

function convertIconVariants(variants: Icon['variants']): Icon_1_IconVariant[] {
return variants.map((variant) => {
const { blobVersionId, mimeType, size, pixelDensity } = variant
return {
blobVersionId: parseVersionId(blobVersionId),
mimeType: convertIconMimeType(mimeType),
size,
pixelDensity: convertIconPixelDensity(pixelDensity),
const { size, blobVersionId } = variant
switch (variant.mimeType) {
case 'image/png':
return {
variant: {
$case: 'pngIcon',
pngIcon: {
pixelDensity: convertIconPixelDensity(variant.pixelDensity),
},
},
size,
blobVersionId: parseVersionId(blobVersionId),
}
case 'image/svg+xml':
return {
variant: {
$case: 'svgIcon',
svgIcon: {},
},
size,
blobVersionId: parseVersionId(blobVersionId),
}
default:
throw new ExhaustivenessError(variant)
}
})
}
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) {
Expand All @@ -186,6 +184,16 @@ function convertIconPixelDensity(pixelDensity: 1 | 2 | 3) {
}
}

export const convertTranslation: ConvertFunction<'translation'> = (
mapeoDoc
) => {
return {
common: convertCommon(mapeoDoc),
...mapeoDoc,
docIdRef: Buffer.from(mapeoDoc.docIdRef, 'hex'),
}
}

function convertCommon(
common: Omit<MapeoCommon, 'versionId'>
): ProtoTypesWithSchemaInfo['common'] {
Expand Down Expand Up @@ -263,7 +271,7 @@ function convertTagPrimitive(
}
break
default:
const _exhaustiveCheck: never = tagPrimitive
throw new ExhaustivenessError(tagPrimitive)
}
return { kind }
}
6 changes: 6 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { type ProtoTypeNames } from '../proto/types.js'
import { type ValidSchemaDef } from '../types.js'

export class ExhaustivenessError extends Error {
constructor(value: never) {
super(`Exhaustiveness check failed. ${value} should be impossible`)
}
}

/**
* Get the name of the type, e.g. `Observation_5` for schemaName `observation`
* and schemaVersion `1`
Expand Down
1 change: 0 additions & 1 deletion test/fixtures/good-docs-completed.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ export const goodDocsCompleted = [
},
{
size: 'large',
pixelDensity: 3,
blobVersionId: randomBytes(32).toString('hex') + '/0',
mimeType: 'image/svg+xml',
},
Expand Down
Loading
Loading