Repository: FHIR/sushi (ts)

Issue:
Logical/Resource root element cannot have empty/missing definition
For `Logical` and `Resource` entities only:

If the user does not provide a caret rule to set the root element's `short`/`definition`, the SUSHI will default the `short` to the specified `Title` and the `definition` to the specified `Description`.  In this case, if `Title` is not specified, then the root element will not have a `short`; and likewise, if `Description` is not specified, then the root element will not have a `definition`.

The problem is that [sdf-3](http://hl7.org/fhir/R4/structuredefinition.html#invs) states that _every_ element must have a `definition` -- so if SUSHI leaves it out, then the IG Publisher complains (rightfully so).  To avoid this, SUSHI should default the `short` and `definition` to the logical/resource _name_ when there otherwise would be no other value for them.

So...

Root element `short`:
* Use `^short` value if specified;
* Otherwise, use `Title:` value if specified;
* Otherwise, use name (specified after `Logical:` or `Resource:`)

Root element `definition`:
* Use `^definition` value if specified;
* Otherwise, use `Description:` value if specified;
* Otherwise, use the `short` value (following logic above, if necessary)

Pull request description:
logical/resource root elements set default to prevent empty/missing definition
Fixes #1159 

This PR includes:
- Logical/Resource root element `short` and `definition` values were set by logic described in 1159 and prevents the case of empty/missing `definition`.
- Affected tests were modified.

Patch:
diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts
index 9b6a447..09ff205 100644
--- a/src/export/StructureDefinitionExporter.ts
+++ b/src/export/StructureDefinitionExporter.ts
@@ -705,12 +705,8 @@ export class StructureDefinitionExporter implements Fishable {
     // differential root element.
 
     // Reset the root element's short and definition
-    if (fshDefinition.title) {
-      structDef.elements[0].short = fshDefinition.title;
-    }
-    if (fshDefinition.description) {
-      structDef.elements[0].definition = fshDefinition.description;
-    }
+    structDef.elements[0].short = fshDefinition.title ?? fshDefinition.name;
+    structDef.elements[0].definition = fshDefinition.description ?? structDef.elements[0].short;
   }
 
   /**


Test patch:
diff --git a/test/export/StructureDefinition.LogicalExporter.test.ts b/test/export/StructureDefinition.LogicalExporter.test.ts
index 68e303d..c399f2d 100644
--- a/test/export/StructureDefinition.LogicalExporter.test.ts
+++ b/test/export/StructureDefinition.LogicalExporter.test.ts
@@ -861,6 +861,73 @@ describe('LogicalExporter', () => {
     expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
   });
 
+  it('should create Logical root element with short equal to title if short not available AND definition equal to description if definition not available', () => {
+    const logical = new Logical('MyTestModel');
+    logical.id = 'MyModel';
+    logical.title = 'MyTestModel title is here';
+    logical.description = 'MyTestModel description is here';
+
+    doc.logicals.set(logical.name, logical);
+    exporter.exportStructDef(logical);
+    const exported = pkg.logicals[0];
+
+    expect(exported.elements).toHaveLength(1);
+    expect(exported.elements[0].short).toBe(logical.title);
+    expect(exported.elements[0].definition).toBe(logical.description);
+  });
+
+  it('should create Logical root element with short equal to name if short and title not available AND definition equal to name if description and definition not available', () => {
+    const logical = new Logical('MyTestModel');
+    logical.id = 'MyModel';
+
+    doc.logicals.set(logical.name, logical);
+    exporter.exportStructDef(logical);
+    const exported = pkg.logicals[0];
+
+    expect(exported.elements).toHaveLength(1);
+    expect(exported.elements[0].short).toBe(logical.name);
+    expect(exported.elements[0].definition).toBe(logical.name);
+  });
+
+  it('should create Logical root element with short equal to title if short not available AND definition equal to short if description and definition not available', () => {
+    const logical = new Logical('MyTestModel');
+    logical.id = 'MyModel';
+    logical.title = 'MyTestModel title is here';
+
+    doc.logicals.set(logical.name, logical);
+    exporter.exportStructDef(logical);
+    const exported = pkg.logicals[0];
+
+    expect(exported.elements).toHaveLength(1);
+    expect(exported.elements[0].short).toBe(logical.title);
+    expect(exported.elements[0].definition).toBe(exported.elements[0].short);
+  });
+
+  it('should create Logical root element with short equal short caret rule AND definition equal to definition caret rule', () => {
+    const logical = new Logical('MyTestModel');
+    logical.id = 'MyModel';
+    logical.title = 'MyTestModel title is here';
+    logical.description = 'MyTestModel description is here';
+
+    const caretValueRule = new CaretValueRule('.');
+    caretValueRule.caretPath = 'short';
+    caretValueRule.value = 'Caret short value is here';
+    logical.rules.push(caretValueRule);
+
+    const caretValueRuleTwo = new CaretValueRule('.');
+    caretValueRuleTwo.caretPath = 'definition';
+    caretValueRuleTwo.value = 'Caret definition value is here';
+    logical.rules.push(caretValueRuleTwo);
+
+    doc.logicals.set(logical.name, logical);
+    exporter.exportStructDef(logical);
+    const exported = pkg.logicals[0];
+
+    expect(exported.elements).toHaveLength(1);
+    expect(exported.elements[0].short).toBe('Caret short value is here');
+    expect(exported.elements[0].definition).toBe('Caret definition value is here');
+  });
+
   describe('#with-type-characteristics-codes', () => {
     let extraDefs: FHIRDefinitions;
 
diff --git a/test/export/StructureDefinition.ResourceExporter.test.ts b/test/export/StructureDefinition.ResourceExporter.test.ts
index 0b8755b..c1cb328 100644
--- a/test/export/StructureDefinition.ResourceExporter.test.ts
+++ b/test/export/StructureDefinition.ResourceExporter.test.ts
@@ -506,4 +506,66 @@ describe('ResourceExporter', () => {
       /non-conformant Resource.*- SupercalifragilisticexpialidociousIsSurprisinglyNotEvenLon\.\.\./s
     );
   });
+
+  it('should create Resource root element with short equal to title if short not available AND definition equal to description if definition not available', () => {
+    const resource = new Resource('MyTestModel');
+    resource.id = 'MyModel';
+    resource.title = 'MyTestModel title is here';
+    resource.description = 'MyTestModel description is here';
+
+    doc.resources.set(resource.name, resource);
+    exporter.exportStructDef(resource);
+    const exported = pkg.resources[0];
+
+    expect(exported.elements[0].short).toBe(resource.title);
+    expect(exported.elements[0].definition).toBe(resource.description);
+  });
+
+  it('should create Resource root element with short equal to name if short and title not available AND definition equal to name if description and definition not available', () => {
+    const resource = new Resource('MyTestModel');
+    resource.id = 'MyModel';
+
+    doc.resources.set(resource.name, resource);
+    exporter.exportStructDef(resource);
+    const exported = pkg.resources[0];
+
+    expect(exported.elements[0].short).toBe(resource.name);
+    expect(exported.elements[0].definition).toBe(resource.name);
+  });
+
+  it('should create Resource root element with short equal to title if short not available AND definition equal to short if description and definition not available', () => {
+    const resource = new Resource('MyTestModel');
+    resource.id = 'MyModel';
+    resource.title = 'MyTestModel title is here';
+
+    doc.resources.set(resource.name, resource);
+    exporter.exportStructDef(resource);
+    const exported = pkg.resources[0];
+
+    expect(exported.elements[0].short).toBe(resource.title);
+    expect(exported.elements[0].definition).toBe(exported.elements[0].short);
+  });
+  it('should create Resource root element with short equal short caret rule AND definition equal to definition caret rule', () => {
+    const resource = new Resource('MyTestModel');
+    resource.id = 'MyModel';
+    resource.title = 'MyTestModel title is here';
+    resource.description = 'MyTestModel description is here';
+
+    const caretValueRule = new CaretValueRule('.');
+    caretValueRule.caretPath = 'short';
+    caretValueRule.value = 'Caret short value is here';
+    resource.rules.push(caretValueRule);
+
+    const caretValueRuleTwo = new CaretValueRule('.');
+    caretValueRuleTwo.caretPath = 'definition';
+    caretValueRuleTwo.value = 'Caret definition value is here';
+    resource.rules.push(caretValueRuleTwo);
+
+    doc.resources.set(resource.name, resource);
+    exporter.exportStructDef(resource);
+    const exported = pkg.resources[0];
+
+    expect(exported.elements[0].short).toBe('Caret short value is here');
+    expect(exported.elements[0].definition).toBe('Caret definition value is here');
+  });
 });
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index aa097ea..e159e56 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -677,8 +677,8 @@ describe('StructureDefinition', () => {
         const expectedSnapshotRootElement = {
           id: 'MyModel',
           path: 'MyModel',
-          short: 'Base for all types and resources',
-          definition: 'Base definition for all types defined in FHIR type system.',
+          short: 'MyTestModel',
+          definition: 'MyTestModel',
           min: 0,
           max: '*',
           base: {
@@ -702,7 +702,9 @@ describe('StructureDefinition', () => {
 
         const expectedDifferentialRootElement = {
           id: 'MyModel',
-          path: 'MyModel'
+          path: 'MyModel',
+          short: 'MyTestModel',
+          definition: 'MyTestModel'
         };
         expect(json.differential.element[0]).toStrictEqual(expectedDifferentialRootElement);
       });