segmentio / typewriter

Type safety + intellisense for your Segment analytics
https://segment.com/docs/protocols/typewriter/
MIT License
227 stars 53 forks source link

Error: strict mode: unknown keyword: "labels" #277

Closed tobad357 closed 1 year ago

tobad357 commented 1 year ago

Validation error when generating plan

Step 1 Setup a plan in Segment UI and add labels to the events

Step 2 Run

node -v
v16.14.0
CI=true npx typewriter init

Step 3 Receive error Error: strict mode: unknown keyword: "labels" which seems to come from AJV Full Error Below

Step 4 Run npm typewriter and generation seems to work fine

{
  "type": "Typewriter JSON Schema Validation Error",
  "description": "You made an analytics call (Command Error) using Typewriter that doesn't match the Tracking Plan spec.",
  "errors": [
    {
      "instancePath": "",
      "schemaPath": "#/required",
      "keyword": "required",
      "params": {
        "missingProperty": "isCI"
      },
      "message": "must have required property 'isCI'",
      "schema": [
        "isCI",
        "rawCommand",
        "errorMessage",
        "error"
      ],
      "parentSchema": {
        "$id": "Command Error",
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "config": {
            "$id": "/properties/config",
            "description": "Local Workspace configuration",
            "properties": {
              "language": {
                "$id": "/properties/properties/properties/config/properties/language",
                "description": "Language to generate",
                "type": "string"
              },
              "languageOptions": {
                "$id": "/properties/properties/properties/config/properties/languageOptions",
                "description": "Advanced Language Options",
                "type": "object"
              },
              "sdk": {
                "$id": "/properties/properties/properties/config/properties/sdk",
                "description": "SDK to generate",
                "type": "string"
              },
              "tokenType": {
                "$id": "/properties/properties/properties/config/properties/tokenType",
                "description": "Type of token retrieval",
                "enum": [
                  "global",
                  "input",
                  "script"
                ],
                "type": "string"
              },
              "trackingPlans": {
                "$id": "/properties/properties/properties/config/properties/trackingPlans",
                "description": "Tracking Plans to generate code for",
                "items": {
                  "$id": "/properties/properties/properties/config/properties/trackingPlans/items",
                  "description": "",
                  "properties": {
                    "id": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/id",
                      "description": "Tracking Plan ID",
                      "type": "string"
                    },
                    "path": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/path",
                      "description": "Path to output code",
                      "type": "string"
                    }
                  },
                  "required": [],
                  "type": "object"
                },
                "type": "array"
              }
            },
            "required": [
              "trackingPlans",
              "language",
              "sdk"
            ],
            "type": "object"
          },
          "error": {
            "$id": "/properties/error",
            "description": "Error Object",
            "type": "object"
          },
          "errorCode": {
            "$id": "/properties/errorCode",
            "description": "Exit code for the error",
            "type": "number"
          },
          "errorMessage": {
            "$id": "/properties/errorMessage",
            "description": "User friendly error message",
            "type": "string"
          },
          "isCI": {
            "$id": "/properties/isCI",
            "description": "Runs in a CI environment",
            "type": "string"
          },
          "rawCommand": {
            "$id": "/properties/rawCommand",
            "description": "Raw command string input",
            "type": "string"
          },
          "workspace": {
            "$id": "/properties/workspace",
            "description": "User Segment Workspace",
            "type": "string"
          }
        },
        "required": [
          "isCI",
          "rawCommand",
          "errorMessage",
          "error"
        ],
        "type": "object"
      },
      "data": {}
    },
    {
      "instancePath": "",
      "schemaPath": "#/required",
      "keyword": "required",
      "params": {
        "missingProperty": "rawCommand"
      },
      "message": "must have required property 'rawCommand'",
      "schema": [
        "isCI",
        "rawCommand",
        "errorMessage",
        "error"
      ],
      "parentSchema": {
        "$id": "Command Error",
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "config": {
            "$id": "/properties/config",
            "description": "Local Workspace configuration",
            "properties": {
              "language": {
                "$id": "/properties/properties/properties/config/properties/language",
                "description": "Language to generate",
                "type": "string"
              },
              "languageOptions": {
                "$id": "/properties/properties/properties/config/properties/languageOptions",
                "description": "Advanced Language Options",
                "type": "object"
              },
              "sdk": {
                "$id": "/properties/properties/properties/config/properties/sdk",
                "description": "SDK to generate",
                "type": "string"
              },
              "tokenType": {
                "$id": "/properties/properties/properties/config/properties/tokenType",
                "description": "Type of token retrieval",
                "enum": [
                  "global",
                  "input",
                  "script"
                ],
                "type": "string"
              },
              "trackingPlans": {
                "$id": "/properties/properties/properties/config/properties/trackingPlans",
                "description": "Tracking Plans to generate code for",
                "items": {
                  "$id": "/properties/properties/properties/config/properties/trackingPlans/items",
                  "description": "",
                  "properties": {
                    "id": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/id",
                      "description": "Tracking Plan ID",
                      "type": "string"
                    },
                    "path": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/path",
                      "description": "Path to output code",
                      "type": "string"
                    }
                  },
                  "required": [],
                  "type": "object"
                },
                "type": "array"
              }
            },
            "required": [
              "trackingPlans",
              "language",
              "sdk"
            ],
            "type": "object"
          },
          "error": {
            "$id": "/properties/error",
            "description": "Error Object",
            "type": "object"
          },
          "errorCode": {
            "$id": "/properties/errorCode",
            "description": "Exit code for the error",
            "type": "number"
          },
          "errorMessage": {
            "$id": "/properties/errorMessage",
            "description": "User friendly error message",
            "type": "string"
          },
          "isCI": {
            "$id": "/properties/isCI",
            "description": "Runs in a CI environment",
            "type": "string"
          },
          "rawCommand": {
            "$id": "/properties/rawCommand",
            "description": "Raw command string input",
            "type": "string"
          },
          "workspace": {
            "$id": "/properties/workspace",
            "description": "User Segment Workspace",
            "type": "string"
          }
        },
        "required": [
          "isCI",
          "rawCommand",
          "errorMessage",
          "error"
        ],
        "type": "object"
      },
      "data": {}
    },
    {
      "instancePath": "",
      "schemaPath": "#/required",
      "keyword": "required",
      "params": {
        "missingProperty": "errorMessage"
      },
      "message": "must have required property 'errorMessage'",
      "schema": [
        "isCI",
        "rawCommand",
        "errorMessage",
        "error"
      ],
      "parentSchema": {
        "$id": "Command Error",
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "config": {
            "$id": "/properties/config",
            "description": "Local Workspace configuration",
            "properties": {
              "language": {
                "$id": "/properties/properties/properties/config/properties/language",
                "description": "Language to generate",
                "type": "string"
              },
              "languageOptions": {
                "$id": "/properties/properties/properties/config/properties/languageOptions",
                "description": "Advanced Language Options",
                "type": "object"
              },
              "sdk": {
                "$id": "/properties/properties/properties/config/properties/sdk",
                "description": "SDK to generate",
                "type": "string"
              },
              "tokenType": {
                "$id": "/properties/properties/properties/config/properties/tokenType",
                "description": "Type of token retrieval",
                "enum": [
                  "global",
                  "input",
                  "script"
                ],
                "type": "string"
              },
              "trackingPlans": {
                "$id": "/properties/properties/properties/config/properties/trackingPlans",
                "description": "Tracking Plans to generate code for",
                "items": {
                  "$id": "/properties/properties/properties/config/properties/trackingPlans/items",
                  "description": "",
                  "properties": {
                    "id": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/id",
                      "description": "Tracking Plan ID",
                      "type": "string"
                    },
                    "path": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/path",
                      "description": "Path to output code",
                      "type": "string"
                    }
                  },
                  "required": [],
                  "type": "object"
                },
                "type": "array"
              }
            },
            "required": [
              "trackingPlans",
              "language",
              "sdk"
            ],
            "type": "object"
          },
          "error": {
            "$id": "/properties/error",
            "description": "Error Object",
            "type": "object"
          },
          "errorCode": {
            "$id": "/properties/errorCode",
            "description": "Exit code for the error",
            "type": "number"
          },
          "errorMessage": {
            "$id": "/properties/errorMessage",
            "description": "User friendly error message",
            "type": "string"
          },
          "isCI": {
            "$id": "/properties/isCI",
            "description": "Runs in a CI environment",
            "type": "string"
          },
          "rawCommand": {
            "$id": "/properties/rawCommand",
            "description": "Raw command string input",
            "type": "string"
          },
          "workspace": {
            "$id": "/properties/workspace",
            "description": "User Segment Workspace",
            "type": "string"
          }
        },
        "required": [
          "isCI",
          "rawCommand",
          "errorMessage",
          "error"
        ],
        "type": "object"
      },
      "data": {}
    },
    {
      "instancePath": "",
      "schemaPath": "#/required",
      "keyword": "required",
      "params": {
        "missingProperty": "error"
      },
      "message": "must have required property 'error'",
      "schema": [
        "isCI",
        "rawCommand",
        "errorMessage",
        "error"
      ],
      "parentSchema": {
        "$id": "Command Error",
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "config": {
            "$id": "/properties/config",
            "description": "Local Workspace configuration",
            "properties": {
              "language": {
                "$id": "/properties/properties/properties/config/properties/language",
                "description": "Language to generate",
                "type": "string"
              },
              "languageOptions": {
                "$id": "/properties/properties/properties/config/properties/languageOptions",
                "description": "Advanced Language Options",
                "type": "object"
              },
              "sdk": {
                "$id": "/properties/properties/properties/config/properties/sdk",
                "description": "SDK to generate",
                "type": "string"
              },
              "tokenType": {
                "$id": "/properties/properties/properties/config/properties/tokenType",
                "description": "Type of token retrieval",
                "enum": [
                  "global",
                  "input",
                  "script"
                ],
                "type": "string"
              },
              "trackingPlans": {
                "$id": "/properties/properties/properties/config/properties/trackingPlans",
                "description": "Tracking Plans to generate code for",
                "items": {
                  "$id": "/properties/properties/properties/config/properties/trackingPlans/items",
                  "description": "",
                  "properties": {
                    "id": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/id",
                      "description": "Tracking Plan ID",
                      "type": "string"
                    },
                    "path": {
                      "$id": "/properties/properties/properties/config/properties/trackingPlans/items/properties/path",
                      "description": "Path to output code",
                      "type": "string"
                    }
                  },
                  "required": [],
                  "type": "object"
                },
                "type": "array"
              }
            },
            "required": [
              "trackingPlans",
              "language",
              "sdk"
            ],
            "type": "object"
          },
          "error": {
            "$id": "/properties/error",
            "description": "Error Object",
            "type": "object"
          },
          "errorCode": {
            "$id": "/properties/errorCode",
            "description": "Exit code for the error",
            "type": "number"
          },
          "errorMessage": {
            "$id": "/properties/errorMessage",
            "description": "User friendly error message",
            "type": "string"
          },
          "isCI": {
            "$id": "/properties/isCI",
            "description": "Runs in a CI environment",
            "type": "string"
          },
          "rawCommand": {
            "$id": "/properties/rawCommand",
            "description": "Raw command string input",
            "type": "string"
          },
          "workspace": {
            "$id": "/properties/workspace",
            "description": "User Segment Workspace",
            "type": "string"
          }
        },
        "required": [
          "isCI",
          "rawCommand",
          "errorMessage",
          "error"
        ],
        "type": "object"
      },
      "data": {}
    }
  ]
}
    Error: strict mode: unknown keyword: "labels"
vidartrojenborg commented 1 year ago

I have the same issue, but I'm just running npx typewriter init, javascript and node.js as language options.

tobad357 commented 1 year ago

I got the same when running in dev mode, however if I changed the function below in the generated code https://github.com/segmentio/typewriter/blob/83b543bcdf2c464272b59b239f7117f4913a5e05/src/languages/templates/typescript/analytics-js.hbs#L195

from

function validateAgainstSchema(
  message: TrackMessage<Record<string, any>>,
  schema: object
) {
  const ajv = new Ajv({ allErrors: true, verbose: true });

  if (!ajv.validate(schema, message.properties) && ajv.errors) {
    onViolation(message, ajv.errors);
  }
}

to

function validateAgainstSchema(
  message: TrackMessage<Record<string, any>>,
  schema: object
) {
  const ajv = new Ajv({ allErrors: true, verbose: true });
  ajv.addKeyword("labels");

  if (!ajv.validate(schema, message.properties) && ajv.errors) {
    onViolation(message, ajv.errors);
  }
}

Then it works

oscb commented 1 year ago

Thanks for reporting this. The last update came in with the validation that should've been disabled for production. Pushing a fix right now.

oscb commented 1 year ago

The latest release should fix these errors: https://github.com/segmentio/typewriter/releases/tag/v8.0.10

Ping me if this still happens to you!

tobad357 commented 1 year ago

@oscb still happens to me

  1. I upgraded to v8.0.10
  2. I removed my generated client and regenerated it
  3. I get same issue

here is an example event that's been generated

 export function contactAdded(
    message: TrackMessage<ContactAdded>,
    callback?: Callback
): void {
    const event = withTypewriterContext({
        ...message,
        event: 'Contact Added',
        properties: {
            ...message.properties,
        },
    });
    const schema = {"$id":"Contact Added","$schema":"http://json-schema.org/draft-07/schema#","description":"Player adds a contact","labels":{"area":"account","type":"contacts"},"properties":{"id":{"$id":"/properties/id","description":"the user contact id","type":"string"},"type":{"$id":"/properties/type","description":"The type of contact (MOBILE,EMAIL,ADDRESS)","enum":["ADDRESS","EMAIL","MOBILE"],"type":"string"}},"type":"object"};
    validateAgainstSchema(event, schema);

    const a = analytics()
    if (a) {
        a.track(event,callback);
    } else {
        throw missingAnalyticsNodeError
    }
}

and the validateAgainstSchema function

function validateAgainstSchema(
    message: TrackMessage<Record<string, any>>,
    schema: object
) {
    const ajv = new Ajv({ allErrors: true, verbose: true })

    if (!ajv.validate(schema, message.properties) && ajv.errors) {
        onViolation(message, ajv.errors)
    }
}

The error is

Error: strict mode: unknown keyword: "labels"
wenga86 commented 1 year ago

+1 this