Skip to content

Instantly share code, notes, and snippets.

@binura-g
Last active September 24, 2025 00:52
Show Gist options
  • Save binura-g/5f74923d06706e47fbc79723016bcfed to your computer and use it in GitHub Desktop.
Save binura-g/5f74923d06706e47fbc79723016bcfed to your computer and use it in GitHub Desktop.

Comparing schema validations between jsonnet and CUE.

Jsonnet requires validation functions to be written - with CUE it's built it and the result is much less verbose.

Jsonnet validation example (can be inlined with the schema, but gets verbose. Best split into a different file).

local std = std;

{
  // Public entry: pass a config object `v`
  webApp(v):

    // -------- helpers --------
    local has(o, k) = std.objectHas(o, k);
    local orDefault(o, k, d) = if has(o, k) then o[k] else d;
    local lower(x) = std.toLower(x);
    local uniqueInts(xs) = std.sort(std.set(xs));

    // Return a list of descriptive error strings about v (no throw yet)
    local validate(v) =
      local errs = [];

      // required top-level fields
      local errs = errs + (if has(v, 'name')   && v.name   != '' then [] else ['missing field: name']);
      local errs = errs + (if has(v, 'image')  && v.image  != '' then [] else ['missing field: image']);
      local errs = errs + (if has(v, 'endpoints') && std.isArray(v.endpoints) && std.length(v.endpoints) > 0
                           then [] else ['endpoints: must be a non-empty array']);

      // short-circuit if endpoints absent/invalid (avoid cascading)
      local eps = if has(v, 'endpoints') && std.isArray(v.endpoints) then v.endpoints else [];

      // per-endpoint requireds
      local errs =
        errs + [
          std.sprintf('endpoints[%d]: missing field: %s', i,
            if !has(e, 'name') then 'name'
            else if !has(e, 'host') then 'host'
            else if !has(e, 'path') then 'path'
            else if !has(e, 'port') then 'port'
            else ''
          )
          for i in std.range(0, std.length(eps)-1)
          for e in [eps[i]]
          if !(has(e,'name') && has(e,'host') && has(e,'path') && has(e,'port'))
        ];

      // per-endpoint value checks
      local errs =
        errs + [
          std.sprintf('endpoints[%d]: name must be non-empty', i)
          for i in std.range(0, std.length(eps)-1)
          for e in [eps[i]]
          if has(e,'name') && e.name == ''
        ] + [
          std.sprintf('endpoints[%d]: path must start with "/"', i)
          for i in std.range(0, std.length(eps)-1)
          for e in [eps[i]]
          if has(e,'path') && (std.length(e.path) == 0 || std.substr(e.path, 0, 1) != '/')
        ] + [
          std.sprintf('endpoints[%d]: port must be 1..65535', i)
          for i in std.range(0, std.length(eps)-1)
          for e in [eps[i]]
          if has(e,'port') && !(e.port >= 1 && e.port <= 65535)
        ] + [
          std.sprintf('endpoints[%d]: type must be "ingress" or "managedAPI" (got %s)', i, e.type)
          for i in std.range(0, std.length(eps)-1)
          for e in [eps[i]]
          if has(e,'type') && !(lower(e.type) == 'ingress' || lower(e.type) == 'managedapi')
        ];

      // duplicate (host,path,type) guard
      local keys = [ std.sprintf('%s|%s|%s', e.host, e.path, lower(orDefault(e,'type','ingress')))
                     for e in eps if has(e,'host') && has(e,'path') ];
      local errs = errs + (if std.length(std.set(keys)) == std.length(keys) then [] else
                           ['endpoints: duplicate (host,path,type) entries detected']);

      // OK if zero errors
      errs
    ;

    // -------- run validation; throw if errors --------
    local _errs = validate(v);
    local _ = assert std.length(_errs) == 0 : 'webApp validation failed:\n- ' + std.join('\n- ', _errs);

    // -------- safe, normalized view of v --------
    local name         = v.name;
    local image        = v.image;
    local replicas     = orDefault(v, 'replicas', 1);
    local ingressClass = orDefault(v, 'ingressClass', 'nginx');
    local serviceType  = orDefault(v, 'serviceType', 'ClusterIP');
    local gatewayName  = orDefault(v, 'gatewayName', 'default-gateway');
    local endpoints    = v.endpoints;

    // unique ports (ints)
    local portsUnique = uniqueInts([ e.port for e in endpoints ]);

    // -------- k8s objects --------
    local deployment = {
      apiVersion: 'apps/v1',
      kind: 'Deployment',
      metadata: {
        name: name,
        labels: { 'app.kubernetes.io/name': name },
      },
      spec: {
        replicas: replicas,
        selector: { matchLabels: { 'app.kubernetes.io/name': name } },
        template: {
          metadata: { labels: { 'app.kubernetes.io/name': name } },
          spec: {
            containers: [{
              name: 'app',
              image: image,
              ports: [{ name: 'p' + std.toString(p), containerPort: p } for p in portsUnique],
            }],
          },
        },
      },
    };

    local service = {
      apiVersion: 'v1',
      kind: 'Service',
      metadata: {
        name: name,
        labels: { 'app.kubernetes.io/name': name },
      },
      spec: {
        type: serviceType,
        selector: { 'app.kubernetes.io/name': name },
        ports: [{ name: 'p' + std.toString(p), port: p, targetPort: p } for p in portsUnique],
      },
    };

    local ingresses = [
      {
        apiVersion: 'networking.k8s.io/v1',
        kind: 'Ingress',
        metadata: {
          name: name + '-' + e.name,
          labels: { 'app.kubernetes.io/name': name },
          annotations: { 'kubernetes.io/ingress.class': ingressClass },
        },
        spec: {
          rules: [{
            host: e.host,
            http: {
              paths: [{
                path: e.path,
                pathType: 'Prefix',
                backend: { service: { name: name, port: { number: e.port } } },
              }],
            },
          }],
          // Optional TLS per endpoint if you include tlsSecretName in config
          tls: if has(e, 'tlsSecretName') && e.tlsSecretName != '' then
                 [{ secretName: e.tlsSecretName, hosts: [ e.host ] }]
               else null,
        },
      }
      for e in endpoints
      if !has(e,'type') || lower(e.type) == 'ingress'
    ];

    local routes = [
      {
        apiVersion: 'gateway.networking.k8s.io/v1',
        kind: 'HTTPRoute',
        metadata: {
          name: name + '-' + e.name,
          labels: { 'app.kubernetes.io/name': name },
        },
        spec: {
          parentRefs: [{ name: gatewayName }],
          hostnames: [ e.host ],
          rules: [{
            matches: [{ path: { type: 'PathPrefix', value: e.path } }],
            backendRefs: [{ name: name, port: e.port }],
          }],
        },
      }
      for e in endpoints
      if has(e,'type') && lower(e.type) == 'managedapi'
    ];

    // return flat list
    [deployment, service] + ingresses + routes,
}

CUE validation example (can be inlined with the schema definition)

#Endpoint: {
	name: string & != ""
	host: string & != ""
	path: string & != "" & =~ `^/`
	port: int & >= 1 & <= 65535
	// default "ingress", allowed values "ingress" | "managedAPI"
	type?: *"ingress" | "managedAPI"
	// optional per-endpoint TLS for Ingress
	tlsSecretName?: string & != ""
}

#App: {
	name:  string & != ""
	image: string & != ""

	// defaults / options
	replicas?:    *1 | int & >= 1
	ingressClass?: *"nginx" | string
	serviceType?:  *"ClusterIP" | "NodePort" | "LoadBalancer"
	gatewayName?:  *"default-gateway" | string

	// require a non-empty list: first element then "..." means ≥1 entries
	endpoints: [ #Endpoint, ...#Endpoint ]

	// ===== Derived / validation helpers =====

	// canonicalized type for each endpoint (lowercased)
	_canonType: [ for e in endpoints { strings.ToLower(e.type | "ingress") } ]

	// de-dupe guard across (host,path,type)
	_keys: [ for i, e in endpoints { e.host + "|" + e.path + "|" + _canonType[i] } ]

	// Must not have duplicates
	_noDupes: *true | bool & (len(list.Unique(_keys)) == len(_keys))

	// Unique port set for containers/services
	_uniquePorts: list.Unique([ for e in endpoints { e.port } ])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment