File size: 2,320 Bytes
aa89cb5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
export type ObjectLocationSegment =
  | { readonly kind: 'property'; readonly name: string }
  | { readonly kind: 'index'; readonly index: number };

export type JsonFieldNamingProfile = 'camelCase' | 'snake_case' | 'PascalCase';

function lowerCamel(name: string): string {
  const parts = name.split('_');
  return parts[0] + parts.slice(1).map(part => part ? part[0].toUpperCase() + part.slice(1) : '').join('');
}

export function renderJsonFieldName(name: string, profile: JsonFieldNamingProfile): string {
  if (profile === 'snake_case') return name;
  const camel = lowerCamel(name);
  if (profile === 'camelCase' || !camel) return camel;
  return camel[0].toUpperCase() + camel.slice(1);
}

function escapeJsonPointer(value: string): string {
  return value.replace(/~/g, '~0').replace(/\//g, '~1');
}

/** A casing-neutral location expressed with canonical KSML property names. */
export class ObjectLocation {
  private constructor(public readonly segments: readonly ObjectLocationSegment[]) {}

  static root(): ObjectLocation { return new ObjectLocation([]); }
  static property(name: string): ObjectLocation { return ObjectLocation.root().property(name); }

  property(name: string): ObjectLocation {
    return new ObjectLocation([...this.segments, { kind: 'property', name }]);
  }

  index(index: number): ObjectLocation {
    return new ObjectLocation([...this.segments, { kind: 'index', index }]);
  }

  prefixedBy(prefix: ObjectLocation): ObjectLocation {
    return new ObjectLocation([...prefix.segments, ...this.segments]);
  }

  modelPath(): string {
    return this.render(name => name);
  }

  nativePath(): string {
    return this.render(lowerCamel);
  }

  instancePath(profile: JsonFieldNamingProfile = 'camelCase'): string {
    return this.segments.map(segment => segment.kind === 'index'
      ? String(segment.index)
      : escapeJsonPointer(renderJsonFieldName(segment.name, profile))).map(value => `/${value}`).join('');
  }

  toString(): string { return this.nativePath(); }

  private render(propertyName: (name: string) => string): string {
    let result = '';
    for (const segment of this.segments) {
      if (segment.kind === 'index') result += `[${segment.index}]`;
      else result += `${result ? '.' : ''}${propertyName(segment.name)}`;
    }
    return result;
  }
}