@@ -10,8 +10,8 @@ import {
1010} from "@zplab/core/lib/designFile" ;
1111import {
1212 buildVariables ,
13+ completeVariables ,
1314 duplicateVariableIssue ,
14- freeId ,
1515 normalizeLeaves ,
1616 propIssues ,
1717 RAW_GRAPHIC_PROPS ,
@@ -25,7 +25,7 @@ import { designSizeIssue } from "@zplab/core/lib/designLimits";
2525import { getAllLeaves , walkObjects , type LabelObject } from "@zplab/core/types/Group" ;
2626import { errorMessage } from "@zplab/core/lib/errorMessage" ;
2727import { DPMM_VALUES , isDpmm , type DeviceFontLabel , type Dpmm , type LabelConfig } from "@zplab/core/types/LabelConfig" ;
28- import type { Variable , VariableInput } from "@zplab/core/types/Variable" ;
28+ import { variableSchema , type Variable , type VariableInput } from "@zplab/core/types/Variable" ;
2929
3030// Re-exported so the tools keep their names.
3131export { buildVariables , propIssues , typeIssues } ;
@@ -52,7 +52,7 @@ export const variableInputSchema: z.ZodType<VariableInput> = z.object({
5252 fnNumber : z . number ( ) . int ( ) . optional ( ) ,
5353 comment : z . string ( ) . optional ( ) ,
5454} ) ;
55- export type VariableInputJson = VariableInput ;
55+ export type VariableInputJson = Omit < VariableInput , "id" > ;
5656
5757export const createDraftShape = {
5858 widthMm : z . number ( ) . positive ( ) ,
@@ -80,7 +80,12 @@ export interface DesignFileJson {
8080 variables ?: Variable [ ] ;
8181}
8282
83- export const designFileEnvelopeSchema = z . object ( { designFile : z . record ( z . string ( ) , z . unknown ( ) ) } ) ;
83+ /** Smaller models tend to hand nested JSON over as a string, so both forms are the same design. */
84+ export const designFileInputSchema = z
85+ . union ( [ z . record ( z . string ( ) , z . unknown ( ) ) , z . string ( ) ] , { error : "designFile must be the design object or its JSON as a string" } )
86+ . describe ( "The design file as an object, or the same JSON as a string." ) ;
87+
88+ export const designFileEnvelopeSchema = z . object ( { designFile : designFileInputSchema } ) ;
8489/** export_zpl: `metadata` keeps ZPLab's ^FX comments for a lossless re-import. */
8590export const exportZplInputSchema = designFileEnvelopeSchema . extend ( { metadata : z . boolean ( ) . optional ( ) } ) ;
8691
@@ -130,28 +135,56 @@ export function duplicateVariableError(variables: readonly Variable[]): ToolErro
130135 return issue === null ? null : { ok : false , errors : [ issue ] } ;
131136}
132137
133- /** The model addresses variables by name, so a missing id is filled in. Object ids stay
134- * required: patch_design addresses objects by id. */
135- function withVariableIds ( designFile : unknown ) : unknown {
136- if ( ! designFile || typeof designFile !== "object" ) return designFile ;
137- const { variables } = designFile as { variables ?: unknown } ;
138- if ( ! Array . isArray ( variables ) ) return designFile ;
139- const taken = new Set < string > ( ) ;
140- for ( const v of variables ) {
141- if ( v && typeof v === "object" && typeof ( v as { id ?: unknown } ) . id === "string" ) taken . add ( ( v as { id : string } ) . id ) ;
142- }
143- const filled = variables . map ( ( v ) => {
144- if ( ! v || typeof v !== "object" || ( v as { id ?: unknown } ) . id !== undefined ) return v ;
145- const id = freeId ( "var" , taken ) ;
146- taken . add ( id ) ;
147- return { ...( v as object ) , id } ;
138+ const isRecord = ( v : unknown ) : v is Record < string , unknown > => typeof v === "object" && v !== null ;
139+ const isComplete = ( v : unknown ) : v is Variable => variableSchema . safeParse ( v ) . success ;
140+ const isPartial = ( v : unknown ) : v is Record < string , unknown > & { name : string } =>
141+ isRecord ( v ) && typeof v . name === "string" && ! isComplete ( v ) ;
142+ const typed = < T > ( v : unknown , type : "string" | "number" ) : T | undefined => ( typeof v === type ? ( v as T ) : undefined ) ;
143+
144+ /** A partial variable is completed like create_draft's. A mistyped field comes back as sent, so the
145+ * schema names it alone. Duplicates go to the loader's repair. */
146+ function withCompleteVariables ( designFile : unknown ) : { ok : true ; value : unknown } | ToolError {
147+ if ( ! isRecord ( designFile ) || ! Array . isArray ( designFile . variables ) ) return { ok : true , value : designFile } ;
148+ const { variables } = designFile ;
149+ const partial = variables . filter ( isPartial ) ;
150+ if ( partial . length === 0 ) return { ok : true , value : designFile } ;
151+ const inputs = partial . map ( ( v ) => ( {
152+ name : v . name ,
153+ id : typed < string > ( v . id , "string" ) ,
154+ fnNumber : typed < number > ( v . fnNumber , "number" ) ,
155+ defaultValue : typed < string > ( v . defaultValue , "string" ) ,
156+ comment : typed < string > ( v . comment , "string" ) ,
157+ } ) ) ;
158+ const built = completeVariables ( inputs , variables . filter ( isComplete ) ) ;
159+ if ( "error" in built ) return { ok : false , errors : [ built . error ] } ;
160+ let next = 0 ;
161+ const completed = variables . map ( ( v ) => {
162+ if ( ! isPartial ( v ) ) return v ;
163+ const full = built . value [ next ++ ] as unknown as Record < string , unknown > ;
164+ const mistyped = Object . entries ( v ) . filter ( ( [ k , x ] ) => x !== undefined && typeof x !== typeof full [ k ] ) ;
165+ return { ...full , ...Object . fromEntries ( mistyped ) } ;
148166 } ) ;
149- return { ...( designFile as object ) , variables : filled } ;
167+ return { ok : true , value : { ...designFile , variables : completed } } ;
168+ }
169+
170+ function decodeDesignFile ( designFile : unknown ) : { ok : true ; value : unknown } | ToolError {
171+ if ( typeof designFile !== "string" ) return { ok : true , value : designFile } ;
172+ try {
173+ const value : unknown = JSON . parse ( designFile ) ;
174+ if ( typeof value === "string" ) return { ok : false , errors : [ "designFile: the JSON decodes to another string, send the design once" ] } ;
175+ return { ok : true , value } ;
176+ } catch ( e ) {
177+ return { ok : false , errors : [ `designFile: not valid JSON, ${ errorMessage ( e ) } ` ] } ;
178+ }
150179}
151180
152181export function parseEnvelope ( designFile : unknown ) : { ok : true ; value : DesignFile } | ToolError {
182+ const input = decodeDesignFile ( designFile ) ;
183+ if ( ! input . ok ) return input ;
184+ const completed = withCompleteVariables ( input . value ) ;
185+ if ( ! completed . ok ) return completed ;
153186 try {
154- const parsed = parseDesignFile ( JSON . stringify ( withVariableIds ( designFile ) ) ) ;
187+ const parsed = parseDesignFile ( JSON . stringify ( completed . value ) ) ;
155188 if ( ! parsed . ok ) return { ok : false , errors : [ designFileErrors [ parsed . error ] , ...( parsed . issues ?? [ ] ) ] } ;
156189 const issues = labelConfigIssues ( parsed . value . label ) ;
157190 if ( issues . length > 0 ) return { ok : false , errors : issues } ;
0 commit comments