Exemplo n.º 1
0
def build_jadn_deps(schema):
    def ns(
        name, nsids
    ):  # Return namespace if name has a known namespace, otherwise return full name
        nsp = name.split(':')[0]
        return nsp if nsp in nsids else name

    imps = schema['meta']['imports'] if 'imports' in schema['meta'] else []
    items = [(n[0], []) for n in imps]
    nsids = [n[0] for n in imps]
    for tdef in schema['types']:
        deps = []
        if tdef[TTYPE] == 'ArrayOf':
            rtype = topts_s2d(tdef[TOPTS])['rtype']
            if not is_builtin(rtype):
                deps.append(ns(rtype, nsids))
        if len(tdef) > FIELDS and tdef[TTYPE] != 'Enumerated':
            for f in tdef[FIELDS]:
                if not is_builtin(f[FTYPE]):
                    deps.append(ns(f[FTYPE], nsids))
        items.append((tdef[TNAME], deps))
    return items
Exemplo n.º 2
0
 def sym(t):  # Build symbol table based on encoding modes
     symval = [
         t,  # 0: S_TDEF:  JADN type definition
         enctab[t[TTYPE]],  # 1: S_CODEC: Decoder, Encoder, Encoded type
         type('') if verbose_str else
         int,  # 2: S_STYPE: Encoded string type (str or tag)
         [],  # 3: S_FORMAT: Functions that check value constraints
         topts_s2d(t[TOPTS]),  # 4: S_TOPT:  Type Options (dict)
         verbose_str,  # 5: S_VSTR:  Verbose String Identifiers
         {},  # 6: S_DMAP: Encoded field key or enum value to API
         {},  # 7: S_EMAP: API field key or enum value to Encoded
         {}  # 8: S_FLD: Symbol table field entry
     ]
     if t[TTYPE] == 'Record':
         rtype = dict if verbose_rec else list
         symval[S_CODEC] = [_decode_maprec, _encode_maprec, rtype]
     fx = FNAME if verbose_str else FTAG
     if t[TTYPE] in ['Enumerated', 'Choice', 'Map', 'Record']:
         fx, fa = (FTAG,
                   FTAG) if 'compact' in symval[S_TOPT] else (fx, FNAME)
         symval[S_DMAP] = {f[fx]: f[fa] for f in t[FIELDS]}
         symval[S_EMAP] = {f[fa]: f[fx] for f in t[FIELDS]}
         if t[TTYPE] in ['Choice', 'Map', 'Record']:
             symval[S_FLD] = {f[fx]: symf(f) for f in t[FIELDS]}
     elif t[TTYPE] == 'Array':
         symval[S_FLD] = {f[FTAG]: symf(f) for f in t[FIELDS]}
     elif t[TTYPE] == 'ArrayOf':
         opts = symval[S_TOPT]
         amin = opts['min'] if 'min' in opts else 1
         amax = opts['max'] if 'max' in opts and opts[
             'max'] > 0 else self.max_array
         opts.update({'min': amin, 'max': amax})
     fchk = symval[S_TOPT]['format'] if 'format' in symval[
         S_TOPT] else ''
     fcvt = symval[S_TOPT]['cvt'] if 'cvt' in symval[S_TOPT] else ''
     symval[S_FORMAT] = get_format_function(fchk, t[TTYPE], fcvt)
     return symval
Exemplo n.º 3
0
def jadn_check(schema):
    """
    Validate JADN structure against JSON schema,
    Validate JADN structure against JADN schema, then
    Perform additional checks on type definitions
    """

    jsonschema.Draft4Validator(jadn_schema).validate(schema)
    #    with open(os.path.join('schema', 'jadn.jadn')) as f:        # TODO: more robust method for locating JADN definition file
    #        jc = Codec(json.load(f), verbose_rec=True, verbose_str=True)
    #        assert jc.encode('Schema', schema) == schema

    # TODO: raise exception instead of print

    for t in schema[
            'types']:  # datatype definition: TNAME, TTYPE, TOPTS, TDESC, FIELDS
        tt = basetype(t[TTYPE])
        if is_builtin(tt):
            topts = topts_s2d(t[TOPTS])
            vop = {k for k in topts} - {k for k in SUPPORTED_TYPE_OPTIONS[tt]}
            if vop:
                print('Error:', t[TNAME], 'type', tt, 'invalid type option',
                      str(vop))
        else:
            print('Error: Unknown Base Type:', tt, '(' + t[TNAME] +
                  ')')  # TODO: handle if t[TNAME] doesn't exist
            topts = {}
        if tt == 'ArrayOf' and 'rtype' not in topts:
            print('Error:', t[TNAME], '- Missing array element type')
        if 'format' in topts:
            f = topts['format']
            if f not in FORMAT_CHECK or tt != FORMAT_CHECK[f]:
                print('Unsupported value constraint',
                      '"' + topts['format'] + '" on', tt + ':', t[TNAME])
        if 'cvt' in topts:
            f = topts['cvt']
            if f not in FORMAT_CONVERT or tt != FORMAT_CONVERT[f]:
                print('Unsupported String conversion',
                      '"' + topts['cvt'] + '" on', tt + ':', t[TNAME])
        if is_primitive(tt) or tt == 'ArrayOf':
            if len(t) != 4:  # TODO: trace back to base type
                print('Type format error:', t[TNAME], '- type', tt,
                      'cannot have items')
        elif is_builtin(tt):
            if len(t) == 5:
                tags = set()  # TODO: check for name and tag collisions
                n = 3 if tt == 'Enumerated' else 5  # TODO: check Choice min cardinality != 0
                for k, i in enumerate(
                        t[FIELDS]
                ):  # item definition: 0-tag, 1-name, 2-type, 3-options, 4-description
                    tags.update({
                        i[FTAG]
                    })  # or (enumerated): 0-tag, 1-name, 2-description
                    ordinal = tt in ('Array', 'Record')
                    if ordinal and i[FTAG] != k + 1:
                        print('Item tag error:',
                              t[TNAME] + '(' + tt + '):' + i[FNAME], '--',
                              i[FTAG], 'should be', k + 1)
                    if len(i) != n:
                        print('Item format error:', t[TNAME], tt, i[FNAME],
                              '-', len(i), '!=', n)
                    if len(i) > 3 and is_builtin(
                            i[FTYPE]):  # TODO: trace back to builtin types
                        fop = {k
                               for k in fopts_s2d(i[FOPTS])} - {
                                   k
                                   for k in SUPPORTED_FIELD_OPTIONS[i[FTYPE]]
                               }
                        if fop:
                            print('Error:', t[TNAME], ':', i[FNAME], i[FTYPE],
                                  'invalid field option', str(fop))
                    # TODO: check that wildcard name has Choice type, and that there is only one wildcard.
                if len(t[FIELDS]) != len(tags):
                    print('Tag collision', t[TNAME], len(t[FIELDS]), 'items,',
                          len(tags), 'unique tags')
            else:
                print('Type format error:', t[TNAME],
                      '- missing items from compound type', tt)
    return schema
Exemplo n.º 4
0
def table_dumps(jadn, form=DEFAULT_FORMAT):
    """
    Translate JADN schema into other formats

    Column classes for presentation formats:
    n - number (right aligned)
    h - meta header (bold, right aligned)
    s - string (left aligned)
    b - bold (bold, left aligned)
    d - description (left aligned, extra width)
    """
    def _tbegin(to, td, head, cls):
        tor = set(to) - {'compact', 'cvt'}
        id = ''
        h = head
        c = cls
        if 'compact' in to:
            id = '.ID'
            h = [head[0]] + head[2:]
            c = [cls[0]] + cls[2:]
        cvt = '.' + to[
            'cvt'] if 'cvt' in to else ''  # Multipart (Array) string conversion method (.<xxx>)
        tos = ' ' + str([str(k) for k in tor]) if tor else ''
        return type_begin(td[TNAME], td[TTYPE] + id + cvt, tos, h, c)

    def _titem(to, fitems, cls):
        f = fitems
        c = cls
        if 'compact' in to:
            f = [fitems[0]] + fitems[2:]
            f[-1] = '**' + fitems[1] + '**' + (' - ' if fitems[1] and f[-1]
                                               else '') + f[-1]
            c = [cls[0]] + cls[2:]
        return type_item(f, c)

    doc_begin, doc_end, sect, meta_begin, meta_item, meta_end, type_begin, type_item, type_end = wtab[
        form]
    meta = jadn['meta']
    title = meta['module'] + (' v.' + meta['patch']) if 'patch' in meta else ''
    text = doc_begin(title)
    text += meta_begin()
    meta_list = ('title', 'module', 'patch', 'description', 'exports',
                 'imports', 'bounds')
    bn = ('max_msg', 'max_str', 'max_bin', 'max_fields')
    for h in meta_list + tuple(set(meta) - set(meta_list)):
        if h in meta:
            mh = zip(bn, meta[h]) if h == 'bounds' else meta[h]
            text += meta_item(h, mh)
    text += meta_end()

    for td in jadn['types']:
        to = topts_s2d(td[TOPTS])
        tor = set(to) - {
            'min', 'max'
        }  # Remaining type options after known options are processed
        tos = ' ' + str([str(k) for k in tor]) if tor else ''
        rng = ''
        if 'min' in to or 'max' in to:
            lo = to['min'] if 'min' in to else 1
            hi = to['max'] if 'max' in to else 1
            rng = ' [' + multiplicity(lo, hi) + ']'
        if td[TTYPE] in PRIMITIVE_TYPES or not is_builtin(td[TTYPE]):
            cls = ['b', 's', 'd']
            text += type_begin('', None, tos,
                               ['Type Name', 'Type Definition', 'Description'],
                               cls)
            cvt = '.' + to[
                'cvt'] if 'cvt' in to else ''  # Binary string conversion method:
            fmt = ' (' + to['format'] + ')' if 'format' in to else ''
            text += type_item(
                [td[TNAME], td[TTYPE] + cvt + rng + fmt, td[TDESC]], cls)
        elif td[TTYPE] in [
                'ArrayOf', 'MapOf'
        ]:  # In STRUCTURE_TYPES but with no field definitions
            cls = ['b', 's', 'd']
            if td[TTYPE] == 'MapOf':
                rtype = '(' + to['ktype'] + ',' + to['rtype'] + ')'
            else:
                rtype = '(' + to['rtype'] + ')'
            tor = set(to) - {'ktype', 'rtype', 'min', 'max'}
            tos = ' ' + str([str(k) for k in tor]) if tor else ''
            text += type_begin('', None, tos,
                               ['Type Name', 'Type Definition', 'Description'],
                               cls)
            text += type_item([td[TNAME], td[TTYPE] + rtype + rng, td[TDESC]],
                              cls)
        elif td[TTYPE] == 'Enumerated':
            if 'rtype' in to:
                rtype = '.*' + to['rtype']
                tor = set(to) - {'rtype'}
                tos = ' ' + str([str(k) for k in tor]) if tor else ''
                text += type_begin(td[TNAME], td[TTYPE] + rtype, tos, [], [])
            else:
                cls = ['n', 'b', 'd']
                text += _tbegin(to, td, ['ID', 'Name', 'Description'], cls)
                for fd in td[FIELDS]:
                    text += _titem(to, [str(fd[FTAG]), fd[FNAME], fd[EDESC]],
                                   cls)
        else:  # Array, Choice, Map, Record
            cls = ['n', 'b', 's', 'n', 'd']
            if td[TTYPE] == 'Array':
                cls2 = [
                    'n', 's', 'n', 'd'
                ]  # Don't print ".ID" in type name but display fields as compact
                text += _tbegin(to, td, ['ID', 'Type', '#', 'Description'],
                                cls2)
                to.update({'compact': True})
            else:
                text += _tbegin(to, td,
                                ['ID', 'Name', 'Type', '#', 'Description'],
                                cls)
            for fd in td[FIELDS]:
                fo = {'min': 1, 'max': 1}
                fo.update(fopts_s2d(fd[FOPTS]))
                rtype = '.*' if 'rtype' in fo else ''
                text += _titem(to, [
                    str(fd[FTAG]), fd[FNAME], fd[FTYPE] + rtype,
                    multiplicity(fo['min'], fo['max']), fd[FDESC]
                ], cls)
        text += type_end()

    text += doc_end()
    return text
Exemplo n.º 5
0
def jas_dumps(jadn):
    """
    Produce JAS module from JADN structure

    JAS represents features available in both JADN and ASN.1 using ASN.1 syntax, but adds
    extended datatypes (Record, Map) for JADN types not directly representable in ASN.1.
    With appropriate encoding rules (which do not yet exist), SEQUENCE could replace Record.
    Map could be implemented using ASN.1 table constraints, but for the purpose of representing
    JSON objects, the Map first-class type in JAS is easier to use.
    """

    jas = '/*\n'
    hdrs = jadn['meta']
    hdr_list = [
        'module', 'patch', 'title', 'description', 'imports', 'exports',
        'bounds'
    ]
    for h in hdr_list + list(set(hdrs) - set(hdr_list)):
        if h in hdrs:
            if h == 'description':
                jas += fill(hdrs[h],
                            width=80,
                            initial_indent='{0:14} '.format(h + ':'),
                            subsequent_indent=15 * ' ') + '\n'
            elif h == 'imports':
                hh = '{:14} '.format(h + ':')
                for imp in hdrs[h]:
                    jas += hh + '{}: {}\n'.format(*imp)
                    hh = 15 * ' '
            elif h == 'exports':
                jas += '{:14} {}\n'.format(h + ':', ', '.join(hdrs[h]))
            else:
                jas += '{:14} {}\n'.format(h + ':', hdrs[h])
    jas += '*/\n'

    assert set(stype_map) == set(
        PRIMITIVE_TYPES + STRUCTURE_TYPES)  # Ensure type list is up to date
    tolist = [
        'compact', 'cvt', 'ktype', 'rtype', 'min', 'max', 'pattern', 'format'
    ]
    assert set(TYPE_OPTIONS.values()) == set(
        tolist)  # Ensure type options list is up to date
    folist = ['rtype', 'atfield', 'min', 'max', 'etype', 'enum', 'default']
    assert set(FIELD_OPTIONS.values()) == set(
        folist)  # Ensure field options list is up to date
    for td in jadn[
            'types']:  # 0:type name, 1:base type, 2:type opts, 3:type desc, 4:fields
        tname = td[TNAME]
        ttype = basetype(td[TTYPE])
        topts = topts_s2d(td[TOPTS])
        tostr = ''
        if 'min' in topts or 'max' in topts:
            lo = topts['min'] if 'min' in topts else 0
            hi = topts['max'] if 'max' in topts else 0
            range = ''
            if lo or hi:
                range = '(' + str(lo) + '..' + (str(hi) if hi else 'MAX') + ')'
        for opt in tolist:
            if opt in topts:
                ov = topts[opt]
                if opt == 'compact':
                    tostr += '.ID'
                elif opt == 'cvt':
                    if ov not in ('x'):
                        ov = 's:' + ov
                    tostr += '.' + ov
                elif opt == 'rtype':
                    tostr += '(' + ov + ')'
                elif opt == 'ktype':
                    pass  # fix MapOf(ktype, rtype)
                elif opt == 'pattern':
                    tostr += ' (PATTERN ("' + ov + '"))'
                elif opt == 'format':
                    tostr += ' (CONSTRAINED BY {' + ov + '})'
                elif opt in ('min', 'max'):  # TODO fix to handle both
                    if range:
                        if ttype in ('Integer', 'Number'):
                            tostr += ' ' + range
                        elif ttype in ('ArrayOf', 'Binary', 'String'):
                            tostr += ' (Size ' + range + ')'
                        else:
                            assert False  # Should never get here
                    range = ''
                else:
                    tostr += ' %' + opt + ': ' + str(ov) + '%'
        tdesc = '    -- ' + td[TDESC] if td[TDESC] else ''
        jas += '\n' + tname + ' ::= ' + stype(ttype) + tostr
        if len(td) > FIELDS:
            titems = deepcopy(td[FIELDS])
            for n, i in enumerate(
                    titems
            ):  # 0:tag, 1:enum item name, 2:enum item desc  (enumerated), or
                if len(
                        i
                ) > FOPTS:  # 0:tag, 1:field name, 2:field type, 3: field opts, 4:field desc
                    desc = i[FDESC]
                    i[FTYPE] = stype(i[FTYPE])
                else:
                    desc = i[EDESC]
                desc = '    -- ' + desc if desc else ''
                i.append(
                    ',' + desc if n < len(titems) - 1 else
                    (' ' +
                     desc if desc else ''))  # TODO: fix hacked desc for join
            flen = min(
                32,
                max(12,
                    max([len(i[FNAME]) for i in titems]) + 1 if titems else 0))
            jas += ' {' + tdesc + '\n'
            if ttype.lower() == 'enumerated':
                fmt = '    {1:' + str(flen) + '} ({0:d}){3}'
                jas += '\n'.join([fmt.format(*i) for i in titems])
            else:
                fmt = '    {1:' + str(flen) + '} [{0:d}] {2}{3}{4}'
                if ttype.lower() == 'record':
                    fmt = '    {1:' + str(flen) + '} {2}{3}{4}'
                items = []
                for n, i in enumerate(titems):
                    ostr = ''
                    opts = fopts_s2d(i[FOPTS])
                    if 'atfield' in opts:
                        ostr += '.&' + opts['atfield']
                        del opts['atfield']
                    if 'rtype' in opts:
                        ostr += '.*'
                        del opts['rtype']
                    if 'min' in opts:
                        if opts['min'] == 0:  # TODO: handle array fields (max != 1)
                            ostr += ' OPTIONAL'
                        del opts['min']
                    items += [
                        fmt.format(i[FTAG], i[FNAME], i[FTYPE], ostr, i[5]) +
                        (' %' + str(opts) if opts else '')
                    ]
                jas += '\n'.join(items)
            jas += '\n}\n' if titems else '}\n'
        else:
            jas += tdesc + '\n'
    return jas