Add validate util

This commit is contained in:
Eric Bailey
2025-02-06 10:43:09 -06:00
parent 59e32b3005
commit 119f56bd00
12 changed files with 68 additions and 47 deletions
+1
View File
@@ -55,6 +55,7 @@
},
"dependencies": {
"@atproto/api": "0.14.0-next.6",
"@atproto/lexicon": "0.4.6-next.5",
"@bitdrift/react-native": "^0.6.2",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
+4 -6
View File
@@ -4,11 +4,7 @@ import type ViewShot from 'react-native-view-shot'
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
import {createAssetAsync} from 'expo-media-library'
import * as Sharing from 'expo-sharing'
import {
AppBskyGraphDefs,
AppBskyGraphStarterpack,
asPredicate,
} from '@atproto/api'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -22,6 +18,7 @@ import * as Dialog from '#/components/Dialog'
import {DialogControlProps} from '#/components/Dialog'
import {Loader} from '#/components/Loader'
import {QrCode} from '#/components/StarterPack/QrCode'
import * as bsky from '#/types/bsky'
export function QrCodeDialog({
starterPack,
@@ -82,8 +79,9 @@ export function QrCodeDialog({
setIsProcessing(true)
if (
!asPredicate(AppBskyGraphStarterpack.validateRecord)(
!bsky.validate(
starterPack.record,
AppBskyGraphStarterpack.validateRecord,
)
) {
return
+4 -4
View File
@@ -4,9 +4,9 @@ import {
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AppBskyFeedPost,
asPredicate,
} from '@atproto/api'
import * as bsky from '#/types/bsky'
import {isPostInLanguage} from '../../locale/helpers'
import {FALLBACK_MARKER_POST} from './feed/home'
import {ReasonFeedSource} from './feed/types'
@@ -68,7 +68,7 @@ export class FeedViewPostsSlice {
}
if (
!AppBskyFeedPost.isRecord(post.record) ||
!asPredicate(AppBskyFeedPost.validateRecord)(post.record)
!bsky.validate(post.record, AppBskyFeedPost.validateRecord)
) {
return
}
@@ -100,7 +100,7 @@ export class FeedViewPostsSlice {
if (
!AppBskyFeedDefs.isPostView(parent) ||
!AppBskyFeedPost.isRecord(parent.record) ||
!asPredicate(AppBskyFeedPost.validateRecord)(parent.record)
!bsky.validate(parent.record, AppBskyFeedPost.validateRecord)
) {
this.isOrphan = true
return
@@ -142,7 +142,7 @@ export class FeedViewPostsSlice {
if (
!AppBskyFeedDefs.isPostView(root) ||
!AppBskyFeedPost.isRecord(root.record) ||
!asPredicate(AppBskyFeedPost.validateRecord)(root.record)
!bsky.validate(root.record, AppBskyFeedPost.validateRecord)
) {
this.isOrphan = true
return
@@ -1,6 +1,6 @@
import React from 'react'
import {View} from 'react-native'
import {$Typed, asPredicate,ComAtprotoLabelDefs} from '@atproto/api'
import {$Typed, ComAtprotoLabelDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -12,6 +12,7 @@ import {useSession} from '#/state/session'
import {atoms as a, useTheme} from '#/alf'
import * as Toggle from '#/components/forms/Toggle'
import {Text} from '#/components/Typography'
import * as bsky from '#/types/bsky'
export function PwiOptOut() {
const t = useTheme()
@@ -33,9 +34,10 @@ export function PwiOptOut() {
profile,
updates: existing => {
// create labels attr if needed
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = asPredicate(
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate(
existing.labels,
ComAtprotoLabelDefs.validateSelfLabels,
)(existing.labels)
)
? existing.labels
: {
$type: 'com.atproto.label.defs#selfLabels',
+2 -6
View File
@@ -1,9 +1,5 @@
import React from 'react'
import {
AppBskyGraphDefs,
AppBskyGraphStarterpack,
asPredicate,
} from '@atproto/api'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
import {msg} from '@lingui/macro'
@@ -128,7 +124,7 @@ export function Provider({
const createInitialState = (): State => {
if (
starterPack &&
asPredicate(AppBskyGraphStarterpack.validateRecord)(starterPack.record)
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
) {
return {
canNext: true,
+2 -2
View File
@@ -4,7 +4,6 @@ import {
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AppBskyFeedPostgate,
asPredicate,
AtUri,
BskyAgent,
} from '@atproto/api'
@@ -22,6 +21,7 @@ import {
POSTGATE_COLLECTION,
} from '#/state/queries/postgate/util'
import {useAgent} from '#/state/session'
import * as bsky from '#/types/bsky'
export async function getPostgateRecord({
agent,
@@ -63,7 +63,7 @@ export async function getPostgateRecord({
if (
data.value &&
asPredicate(AppBskyFeedPostgate.validateRecord)(data.value)
bsky.validate(data.value, AppBskyFeedPostgate.validateRecord)
) {
return data.value
} else {
+1 -2
View File
@@ -4,7 +4,6 @@ import {
AppBskyGraphGetStarterPack,
AppBskyGraphStarterpack,
AppBskyRichtextFacet,
asPredicate,
AtUri,
BskyAgent,
RichText,
@@ -369,7 +368,7 @@ export async function precacheStarterPack(
starterPackView = starterPack
} else if (
AppBskyGraphDefs.isStarterPackViewBasic(starterPack) &&
asPredicate(AppBskyGraphStarterpack.validateRecord)(starterPack.record)
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
) {
const listView: AppBskyGraphDefs.ListViewBasic = {
uri: starterPack.record.list,
+2 -2
View File
@@ -2,7 +2,6 @@ import {
AppBskyFeedDefs,
AppBskyFeedGetPostThread,
AppBskyFeedThreadgate,
asPredicate,
AtUri,
BskyAgent,
} from '@atproto/api'
@@ -21,6 +20,7 @@ import {
} from '#/state/queries/threadgate/util'
import {useAgent} from '#/state/session'
import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies'
import * as bsky from '#/types/bsky'
export * from '#/state/queries/threadgate/types'
export * from '#/state/queries/threadgate/util'
@@ -141,7 +141,7 @@ export async function getThreadgateRecord({
if (
data.value &&
asPredicate(AppBskyFeedThreadgate.validateRecord)(data.value)
bsky.validate(data.value, AppBskyFeedThreadgate.validateRecord)
) {
return data.value
} else {
+3 -2
View File
@@ -1,6 +1,7 @@
import {AppBskyFeedDefs, AppBskyFeedThreadgate, asPredicate} from '@atproto/api'
import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api'
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types'
import * as bsky from '#/types/bsky'
export function threadgateViewToAllowUISetting(
threadgateView: AppBskyFeedDefs.ThreadgateView | undefined,
@@ -8,7 +9,7 @@ export function threadgateViewToAllowUISetting(
// Validate the record for clarity, since backwards compat code is a little confusing
const threadgate =
threadgateView &&
asPredicate(AppBskyFeedThreadgate.validateRecord)(threadgateView.record)
bsky.validate(threadgateView.record, AppBskyFeedThreadgate.validateRecord)
? threadgateView.record
: undefined
return threadgateRecordToAllowUISetting(threadgate)
+31 -7
View File
@@ -1,19 +1,22 @@
import {asPredicate} from '@atproto/api'
import {ValidationResult} from '@atproto/lexicon'
export * as profile from '#/types/bsky/profile'
export * as starterPack from '#/types/bsky/starterPack'
/**
* Use sparingly, and only when you know it's safe to do so.
* Fast type checking without full schema validation, for use with data we
* trust, or for non-critical path use cases. Why? Our SDK's `is*` identity
* utils do not assert the type of the entire object, only the `$type` string.
*
* Our SDK's `is*` identity utils do not assert the type of the entire object,
* and although the `isValid*` utils do, they also fully validate the object
* shape, which has a performance cost. This util allows us to prescribe the
* type we expect, while only checking the `$type` value of the record.
* For full validation of the object schema, use the `validate` export from
* this file.
*
* Usage:
* ```ts
* import * as atp from '#/types/atproto'
* import * as bsky from '#/types/bsky'
*
* if (atp.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
* if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
* }
* ```
@@ -24,3 +27,24 @@ export function dangerousIsType<R extends {$type?: string}>(
): record is R {
return identity(record)
}
/**
* Fully validates the object schema, which as a performance cost.
*
* For faster checks with data we trust, like that from our app view, use the
* `dangerousIsType` export from this same file.
*
* Usage:
* ```ts
* import * as bsky from '#/types/bsky'
*
* if (bsky.validate(item, AppBskyFeedPost.validateRecord)) {
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
* }
*/
export function validate<R extends {$type?: string}>(
record: unknown,
identity: (v: unknown) => ValidationResult<R>,
): record is R {
return asPredicate(identity)(record)
}
+2 -2
View File
@@ -3,7 +3,6 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
asPredicate,
AtUri,
ModerationDecision,
RichText as RichTextAPI,
@@ -29,6 +28,7 @@ import {atoms as a} from '#/alf'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {RichText} from '#/components/RichText'
import {SubtleWebHover} from '#/components/SubtleWebHover'
import * as bsky from '#/types/bsky'
import {ContentHider} from '../../../components/moderation/ContentHider'
import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
import {PostAlerts} from '../../../components/moderation/PostAlerts'
@@ -54,7 +54,7 @@ export function Post({
const moderationOpts = useModerationOpts()
const record = useMemo<AppBskyFeedPost.Record | undefined>(
() =>
asPredicate(AppBskyFeedPost.validateRecord)(post.record)
bsky.validate(post.record, AppBskyFeedPost.validateRecord)
? post.record
: undefined,
[post],
+11 -11
View File
@@ -282,6 +282,17 @@
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/lexicon@0.4.6-next.5", "@atproto/lexicon@^0.4.6-next.5":
version "0.4.6-next.5"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6-next.5.tgz#433a941cf8902afec15135f70cf2cbed37c35406"
integrity sha512-Id1atQ1rZ3dUGAUyBcauD+zs5lGHSmoAlC1Kwb2DEvkaWMv3oTXUjtdQxyb0/Kz4YaFI+nxLLRH9NBO+BipG+w==
dependencies:
"@atproto/common-web" "^0.3.2"
"@atproto/syntax" "^0.3.1"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/lexicon@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.4.tgz#0d97314bb57b693b76f2495fa5e02872469dd93a"
@@ -304,17 +315,6 @@
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/lexicon@^0.4.6-next.5":
version "0.4.6-next.5"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6-next.5.tgz#433a941cf8902afec15135f70cf2cbed37c35406"
integrity sha512-Id1atQ1rZ3dUGAUyBcauD+zs5lGHSmoAlC1Kwb2DEvkaWMv3oTXUjtdQxyb0/Kz4YaFI+nxLLRH9NBO+BipG+w==
dependencies:
"@atproto/common-web" "^0.3.2"
"@atproto/syntax" "^0.3.1"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/oauth-provider@^0.2.10":
version "0.2.10"
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901"