Repository: FHIR/sushi
Pull request #556 (2020-07-30 12:37:15)

id is not required in FSHOnly mode
The configured id is used when generating an IG, but is not used
otherwise. Do not require it in FSHOnly mode. The two remaining required
configuration values are:
- canonical: used to set the url on generated resources
- fhirVersion: used to download FHIR resources

The version property is no longer required. It is used to set the
version on generated resources, but this is an optional property.


Patch:
diff --git a/src/import/importConfiguration.ts b/src/import/importConfiguration.ts
index f146317..fa4afaa 100644
--- a/src/import/importConfiguration.ts
+++ b/src/import/importConfiguration.ts
@@ -46,8 +46,9 @@ import {
 } from '../fhirtypes';
 import { FshCode } from '../fshtypes';
 
-const MINIMAL_CONFIG_PROPERTIES = ['id', 'version', 'canonical', 'fhirVersion'];
+const MINIMAL_CONFIG_PROPERTIES = ['canonical', 'fhirVersion'];
 // Properties that are only relevant when an IG is going to be generated from output, and have no informational purpose
+const MINIMAL_IG_ONLY_PROPERTIES = ['id', 'name', 'status', 'copyrightYear', 'releaseLabel'];
 const IG_ONLY_PROPERTIES = [
   'contained',
   'extension',
@@ -85,15 +86,27 @@ export function importConfiguration(yaml: YAMLConfiguration | string, file: stri
   }
 
   // There are a few properties that are absolutely required if we are to have *any* success at all
-  if (
-    MINIMAL_CONFIG_PROPERTIES.some(
-      (p: keyof YAMLConfiguration) =>
-        yaml[p] == null || (Array.isArray(yaml[p]) && (yaml[p] as any[]).length === 0)
-    )
-  ) {
+  const minimalProperties = yaml.FSHOnly
+    ? MINIMAL_CONFIG_PROPERTIES
+    : [...MINIMAL_CONFIG_PROPERTIES, ...MINIMAL_IG_ONLY_PROPERTIES];
+  const missingProperties = minimalProperties.filter(
+    (p: keyof YAMLConfiguration) =>
+      yaml[p] == null || (Array.isArray(yaml[p]) && (yaml[p] as any[]).length === 0)
+  );
+  // the copyrightYear and releaseLabel properties permit alternate spellings as all lowercase,
+  // so if only those are missing, check for the lowercase version before logging an error.
+  if (missingProperties.includes('copyrightYear') && yaml.copyrightyear) {
+    missingProperties.splice(missingProperties.indexOf('copyrightYear'), 1);
+  }
+  if (missingProperties.includes('releaseLabel') && yaml.releaselabel) {
+    missingProperties.splice(missingProperties.indexOf('releaseLabel'), 1);
+  }
+  if (missingProperties.length > 0) {
     logger.error(
-      'SUSHI minimally requires the following configuration properties to start processing FSH: ' +
-        MINIMAL_CONFIG_PROPERTIES.join(', ') +
+      `SUSHI minimally requires the following configuration properties to ${
+        yaml.FSHOnly ? 'start processing FSH' : 'generate an IG'
+      }: ` +
+        minimalProperties.join(', ') +
         '.',
       { file }
     );


Test patch:
diff --git a/test/import/importConfiguration.test.ts b/test/import/importConfiguration.test.ts
index fd2f4a5..5a8f41e 100644
--- a/test/import/importConfiguration.test.ts
+++ b/test/import/importConfiguration.test.ts
@@ -228,15 +228,23 @@ describe('importConfiguration', () => {
       const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.id).toBe('my-id');
     });
-    it('should report an error and throw if id is missing', () => {
+
+    it('should report an error and throw if id is missing when FSHOnly is false', () => {
       delete minYAML.id;
       expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
         'Minimal config not met'
       );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /SUSHI minimally requires the following configuration properties to start processing FSH: id, version, canonical, fhirVersion\.\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
     });
+
+    it('should not report an error and throw if id is missing when FSHOnly is true', () => {
+      delete minYAML.id;
+      minYAML.FSHOnly = true;
+      const config = importConfiguration(minYAML, 'test-config.yaml');
+      expect(config.id).toBeUndefined();
+    });
   });
 
   describe('#meta', () => {
@@ -401,13 +409,24 @@ describe('importConfiguration', () => {
       const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.canonical).toBe('http://foo.org/some-canonical-url');
     });
-    it('should report an error and throw if canonical is missing', () => {
+    it('should report an error and throw if canonical is missing and FSHOnly is true', () => {
+      delete minYAML.canonical;
+      minYAML.FSHOnly = true;
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
+      expect(loggerSpy.getLastMessage('error')).toMatch(
+        /SUSHI minimally requires the following configuration properties to start processing FSH: canonical, fhirVersion\.\s*File: test-config\.yaml/
+      );
+    });
+
+    it('should report an error and throw if canonical is missing and FSHOnly is false', () => {
       delete minYAML.canonical;
       expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
         'Minimal config not met'
       );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /SUSHI minimally requires the following configuration properties to start processing FSH: id, version, canonical, fhirVersion\.\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
     });
   });
@@ -438,15 +457,6 @@ describe('importConfiguration', () => {
       const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.version).toBe('1.2');
     });
-    it('should report an error and throw if version is missing', () => {
-      delete minYAML.version;
-      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
-        'Minimal config not met'
-      );
-      expect(loggerSpy.getLastMessage('error')).toMatch(
-        /SUSHI minimally requires the following configuration properties to start processing FSH: id, version, canonical, fhirVersion\.\s*File: test-config\.yaml/
-      );
-    });
   });
 
   describe('#name', () => {
@@ -455,12 +465,20 @@ describe('importConfiguration', () => {
       const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.name).toBe('MyIG');
     });
-    it('should report an error if name is missing', () => {
+    it('should report an error and throw if name is missing and FSHOnly is false', () => {
       delete minYAML.name;
-      const config = importConfiguration(minYAML, 'test-config.yaml');
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /Configuration missing required property: name\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
+    });
+
+    it('should not report an error and throw if name is missing and FSHOnly is true', () => {
+      delete minYAML.name;
+      minYAML.FSHOnly = true;
+      const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.name).toBeUndefined();
     });
   });
@@ -493,12 +511,20 @@ describe('importConfiguration', () => {
       );
       expect(config.status).toBeUndefined();
     });
-    it('should report an error if status is missing', () => {
+    it('should report an error if status is missing and FSHOnly is false', () => {
       delete minYAML.status;
-      const config = importConfiguration(minYAML, 'test-config.yaml');
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /Configuration missing required property: status\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
+    });
+
+    it('should not report an error if status is missing and FSHOnly is true', () => {
+      delete minYAML.status;
+      minYAML.FSHOnly = true;
+      const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.status).toBeUndefined();
     });
   });
@@ -1301,22 +1327,43 @@ describe('importConfiguration', () => {
       const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.fhirVersion).toEqual(['4.0.1']);
     });
-    it('should report an error and throw if fhirVersion is missing', () => {
+    it('should report an error and throw if fhirVersion is missing and FSHOnly is true', () => {
+      delete minYAML.fhirVersion;
+      minYAML.FSHOnly = true;
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
+      expect(loggerSpy.getLastMessage('error')).toMatch(
+        /SUSHI minimally requires the following configuration properties to start processing FSH: canonical, fhirVersion\.\s*File: test-config\.yaml/
+      );
+    });
+    it('should report an error and throw if fhirVersion is an empty array and FSHOnly is true', () => {
+      minYAML.fhirVersion = [];
+      minYAML.FSHOnly = true;
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
+      expect(loggerSpy.getLastMessage('error')).toMatch(
+        /SUSHI minimally requires the following configuration properties to start processing FSH: canonical, fhirVersion\.\s*File: test-config\.yaml/
+      );
+    });
+
+    it('should report an error and throw if fhirVersion is missing and FSHOnly is false', () => {
       delete minYAML.fhirVersion;
       expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
         'Minimal config not met'
       );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /SUSHI minimally requires the following configuration properties to start processing FSH: id, version, canonical, fhirVersion\.\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
     });
-    it('should report an error and throw if fhirVersion is an empty array', () => {
+    it('should report an error and throw if fhirVersion is an empty array and FSHOnly is false', () => {
       minYAML.fhirVersion = [];
       expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
         'Minimal config not met'
       );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /SUSHI minimally requires the following configuration properties to start processing FSH: id, version, canonical, fhirVersion\.\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
     });
   });
@@ -1749,14 +1796,15 @@ describe('importConfiguration', () => {
       config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.parameters[0]).toEqual({ code: 'copyrightyear', value: '2020' });
     });
-    it('should report an error if copyrightYear/copyrightyear is missing and FSHOnly is false', () => {
+    it('should report an error and throw if copyrightYear/copyrightyear is missing and FSHOnly is false', () => {
       delete minYAML.copyrightYear;
       minYAML.FSHOnly = false;
-      const config = importConfiguration(minYAML, 'test-config.yaml');
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /Configuration missing required property: copyrightYear\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
-      expect(config.parameters.find(p => p.code === 'copyrightYear')).toBeUndefined();
     });
     it('should not report an error if copyrightYear/copyrightyear is missing and FSHOnly is true', () => {
       delete minYAML.copyrightYear;
@@ -1779,14 +1827,15 @@ describe('importConfiguration', () => {
       const config = importConfiguration(minYAML, 'test-config.yaml');
       expect(config.parameters[1]).toEqual({ code: 'releaselabel', value: 'STU2' });
     });
-    it('should report an error if releaseLabel/releaselabel is missing and FSHOnly is false', () => {
+    it('should report an error and throw if releaseLabel/releaselabel is missing and FSHOnly is false', () => {
       delete minYAML.releaseLabel;
       minYAML.FSHOnly = false;
-      const config = importConfiguration(minYAML, 'test-config.yaml');
+      expect(() => importConfiguration(minYAML, 'test-config.yaml')).toThrow(
+        'Minimal config not met'
+      );
       expect(loggerSpy.getLastMessage('error')).toMatch(
-        /Configuration missing required property: releaseLabel\s*File: test-config\.yaml/
+        /SUSHI minimally requires the following configuration properties to generate an IG: canonical, fhirVersion, id, name, status, copyrightYear, releaseLabel\.\s*File: test-config\.yaml/
       );
-      expect(config.parameters.find(p => p.code === 'releaseLabel')).toBeUndefined();
     });
     it('should not report an error if releaseLabel/releaselabel is missing and and FSHOnly is true', () => {
       delete minYAML.releaseLabel;
diff --git a/test/utils/Processing.test.ts b/test/utils/Processing.test.ts
index c54dda4..fa2fa7b 100644
--- a/test/utils/Processing.test.ts
+++ b/test/utils/Processing.test.ts
@@ -124,7 +124,17 @@ describe('Processing', () => {
           { packageId: 'hl7.fhir.us.core', version: '3.1.0' },
           { packageId: 'hl7.fhir.uv.vhdir', version: 'current' }
         ],
-        FSHOnly: false
+        FSHOnly: false,
+        parameters: [
+          {
+            code: 'copyrightyear',
+            value: '2020'
+          },
+          {
+            code: 'releaselabel',
+            value: 'CI Build'
+          }
+        ]
       });
     });
 
diff --git a/test/utils/fixtures/valid-yaml/config.yaml b/test/utils/fixtures/valid-yaml/config.yaml
index 6a9a0e5..f3c4464 100644
--- a/test/utils/fixtures/valid-yaml/config.yaml
+++ b/test/utils/fixtures/valid-yaml/config.yaml
@@ -9,6 +9,8 @@ version: 0.1.0
 name: FSHTestIG
 title: FSH Test IG
 status: active
+copyrightYear: 2020
+releaseLabel: CI Build
 publisher: James Tuna
 contact:
   - name: Bill Cod
@@ -22,4 +24,4 @@ license: CC0-1.0
 fhirVersion: 4.0.1
 dependencies:
   hl7.fhir.us.core: 3.1.0
-  hl7.fhir.uv.vhdir: current
\ No newline at end of file
+  hl7.fhir.uv.vhdir: current